Repository: brightway-lca/brightway2-io

Issue:
`rescale_exchange` multiplies `minimum` and `maximum` by `scale` twice
See [line 154](https://github.com/brightway-lca/brightway2-io/blob/642f2500588aeafada3a92e2a934f27287c0af07/bw2io/utils.py#L154) and [line 161](https://github.com/brightway-lca/brightway2-io/blob/642f2500588aeafada3a92e2a934f27287c0af07/bw2io/utils.py#L161).

Discussion:

Patch:
diff --git a/bw2io/strategies/ecospold1_allocation.py b/bw2io/strategies/ecospold1_allocation.py
index f4c217a..6a147bf 100644
--- a/bw2io/strategies/ecospold1_allocation.py
+++ b/bw2io/strategies/ecospold1_allocation.py
@@ -1,5 +1,7 @@
 import copy
 
+from ..utils import rescale_exchange
+
 
 def delete_integer_codes(data):
     """
@@ -167,37 +169,10 @@ def allocate_exchanges(ds):
     for coproduct in coproducts:
         new_ds = copy.deepcopy(ds)
         new_ds["exchanges"] = [
-            rescale_exchange(exchange_dict[exc_id], scale)
+            rescale_exchange(copy.deepcopy(exchange_dict[exc_id]), scale)
             for exc_id, scale in list(multipliers[coproduct["code"]].items())
             # Exclude self-allocation; assume 100%
             if exc_id != coproduct["code"]
         ] + [coproduct]
         new_datasets.append(new_ds)
     return new_datasets
-
-
-def rescale_exchange(exc, scale):
-    """
-    Rescale an exchange by a given factor.
-
-    Parameters
-    ----------
-    exc : dict
-        The exchange to be rescaled.
-    scale : float
-        The factor by which to rescale the exchange.
-
-    Returns
-    -------
-    dict
-        The rescaled exchange.
-
-    Examples
-    --------
-    >>> exc = {'name': 'Output 1', 'amount': 1.0}
-    >>> rescale_exchange(exc, 2.0)
-    {'name': 'Output 1', 'amount': 2.0}
-    """
-    exc = copy.deepcopy(exc)
-    exc["amount"] *= scale
-    return exc
diff --git a/bw2io/utils.py b/bw2io/utils.py
index 0ee1583..ed38a14 100644
--- a/bw2io/utils.py
+++ b/bw2io/utils.py
@@ -5,7 +5,16 @@ import os
 import pprint
 from numbers import Number
 
-from stats_arrays import *
+from stats_arrays import (
+    LognormalUncertainty,
+    NormalUncertainty,
+    NoUncertainty,
+    TriangularUncertainty,
+    UndefinedUncertainty,
+    UniformUncertainty,
+)
+
+from .errors import UnsupportedExchange
 
 DEFAULT_FIELDS = ("name", "categories", "unit", "reference product", "location")
 
@@ -116,7 +125,7 @@ def rescale_exchange(exc: dict, factor: float) -> dict:
         If factor is not a number.
 
     """
-    if not isinstance(factor, Number):
+    if not isinstance(factor, Number) or factor is True or factor is False:
         raise ValueError(f"`factor` must be a number, but got {type(factor)}")
 
     if factor == 0:
@@ -150,23 +159,38 @@ def rescale_exchange(exc: dict, factor: float) -> dict:
                 "amount": exc["amount"] * factor,
             }
         )
-    elif exc["uncertainty type"] in (TriangularUncertainty.id, UniformUncertainty.id):
+    elif exc["uncertainty type"] == UniformUncertainty.id:
         exc["minimum"] *= factor
         exc["maximum"] *= factor
         if "amount" in exc:
             exc["amount"] = exc["loc"] = factor * exc["amount"]
+        else:
+            exc["amount"] = exc["loc"] = (exc["minimum"] + exc["maximum"]) / 2
+    elif exc["uncertainty type"] == TriangularUncertainty.id:
+        exc["minimum"] *= factor
+        exc["maximum"] *= factor
+        exc["amount"] = exc["loc"] = factor * exc["amount"]
     else:
         raise UnsupportedExchange("This exchange type can't be automatically rescaled")
 
-    for field in ("minimum", "maximum"):
-        if field in exc:
-            exc[field] *= factor
+    # negative flag only used in lognormal but can be incorrect if
+    # scale < 0 so best to just delete it
+    if exc.get("uncertainty type") != LognormalUncertainty.id and "negative" in exc:
+        del exc["negative"]
+
+    if exc.get("uncertainty type") not in (
+        TriangularUncertainty.id,
+        UniformUncertainty.id,
+    ):
+        for field in ("minimum", "maximum"):
+            if field in exc:
+                exc[field] *= factor
     if factor < 0 and "minimum" in exc and "maximum" in exc:
         exc["minimum"], exc["maximum"] = exc["maximum"], exc["minimum"]
     elif factor < 0 and "minimum" in exc:
-        exc["maximum"] = exc["minimum"]
+        exc["maximum"] = exc.pop("minimum")
     elif factor < 0 and "maximum" in exc:
-        exc["minimum"] = exc["maximum"]
+        exc["minimum"] = exc.pop("maximum")
 
     return exc
 


Test patch:
diff --git a/tests/strategies/allocation.py b/tests/strategies/allocation.py
index 30abb37..b8a1315 100644
--- a/tests/strategies/allocation.py
+++ b/tests/strategies/allocation.py
@@ -3,71 +3,67 @@ import unittest
 from bw2io.strategies import es1_allocate_multioutput
 
 
-class Ecospold1AllocationTestCase(unittest.TestCase):
-    def test_allocation(self):
-        data = [
-            {
-                "exchanges": [
-                    {
-                        "type": "production",
-                        "code": "p1",
-                        "amount": 1,
-                    },
-                    {
-                        "type": "production",
-                        "code": "p2",
-                        "amount": 2,
-                    },
-                    {
-                        "type": "emission",
-                        "code": "e1",
-                        "amount": 10,
-                    },
-                    {
-                        "type": "technosphere",
-                        "code": "t1",
-                        "amount": 20,
-                    },
-                ],
-                "allocations": [
-                    {
-                        "exchanges": ["e1", "t1"],
-                        "fraction": 50.0,
-                        "reference": "p1",
-                    },
-                    {
-                        "exchanges": ["e1"],
-                        "fraction": 10.0,
-                        "reference": "p2",
-                    },
-                    {
-                        "exchanges": ["t1"],
-                        "fraction": 100.0,
-                        "reference": "p2",
-                    },
-                ],
-            }
-        ]
-        expected = [
-            {
-                "exchanges": [
-                    {"amount": 10 * 0.5, "code": "e1", "type": "emission"},
-                    {"amount": 1, "code": "p1", "type": "production"},
-                    {"amount": 20 * 0.5, "code": "t1", "type": "technosphere"},
-                ],
-            },
-            {
-                "exchanges": [
-                    {"amount": 10 * 0.1, "code": "e1", "type": "emission"},
-                    {"amount": 2, "code": "p2", "type": "production"},
-                    {"amount": 20 * 1.0, "code": "t1", "type": "technosphere"},
-                ],
-            },
-        ]
-        self.maxDiff = None
-        # import pprint
-        # pprint.pprint(es1_allocate_multioutput(data))
-        answer = es1_allocate_multioutput(data)
-        answer[0]["exchanges"].sort(key=lambda x: x["type"])
-        answer[1]["exchanges"].sort(key=lambda x: x["type"])
-        self.assertEqual(answer, expected)
+def test_allocation():
+    data = [
+        {
+            "exchanges": [
+                {
+                    "type": "production",
+                    "code": "p1",
+                    "amount": 1,
+                },
+                {
+                    "type": "production",
+                    "code": "p2",
+                    "amount": 2,
+                },
+                {
+                    "type": "emission",
+                    "code": "e1",
+                    "amount": 10,
+                },
+                {
+                    "type": "technosphere",
+                    "code": "t1",
+                    "amount": 20,
+                },
+            ],
+            "allocations": [
+                {
+                    "exchanges": ["e1", "t1"],
+                    "fraction": 50.0,
+                    "reference": "p1",
+                },
+                {
+                    "exchanges": ["e1"],
+                    "fraction": 10.0,
+                    "reference": "p2",
+                },
+                {
+                    "exchanges": ["t1"],
+                    "fraction": 100.0,
+                    "reference": "p2",
+                },
+            ],
+        }
+    ]
+    expected = [
+        {
+            "exchanges": [
+                {"amount": 10 * 0.5, "code": "e1", "type": "emission", 'loc': 5},
+                {"amount": 1, "code": "p1", "type": "production"},
+                {"amount": 20 * 0.5, "code": "t1", "type": "technosphere", 'loc': 10},
+            ],
+        },
+        {
+            "exchanges": [
+                {"amount": 10 * 0.1, "code": "e1", "type": "emission", "loc": 1},
+                {"amount": 2, "code": "p2", "type": "production"},
+                {"amount": 20 * 1.0, "code": "t1", "type": "technosphere", "loc": 20},
+            ],
+        },
+    ]
+    answer = es1_allocate_multioutput(data)
+    answer[0]["exchanges"].sort(key=lambda x: x["type"])
+    answer[1]["exchanges"].sort(key=lambda x: x["type"])
+    assert answer == expected
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..b6da53f
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,431 @@
+import math
+
+import pytest
+from stats_arrays import (
+    LognormalUncertainty,
+    NormalUncertainty,
+    NoUncertainty,
+    TriangularUncertainty,
+    UndefinedUncertainty,
+    UniformUncertainty,
+)
+
+from bw2io.errors import UnsupportedExchange
+from bw2io.utils import (
+    activity_hash,
+    es2_activity_hash,
+    format_for_logging,
+    load_json_data_file,
+    rescale_exchange,
+    standardize_method_to_len_3,
+)
+
+
+def test_load_json_data():
+    assert load_json_data_file("test") == {"1": {"2": 3}}
+    assert load_json_data_file("test.json") == {"1": {"2": 3}}
+
+
+def test_activity_hash():
+    assert activity_hash({}) == "d41d8cd98f00b204e9800998ecf8427e"
+    assert activity_hash({}) == "d41d8cd98f00b204e9800998ecf8427e"
+    assert isinstance(activity_hash({}), str)
+
+    ds = {"name": "care bears", "unit": "kilogram", "location": "GLO"}
+    assert activity_hash(ds) == "a6d6dd46cc33acd23826fa5b4e83377f"
+
+    ds = {
+        "name": "care bears",
+        "categories": ["toys", "fun"],
+        "unit": "kilogram",
+        "reference product": "lollipops",
+        "location": "GLO",
+        "extra": "irrelevant",
+    }
+    assert activity_hash(ds) == "90d8689ec08dceb9507d28a36df951cd"
+
+    ds = {"name": "正しい馬のバッテリーの定番"}
+    assert activity_hash(ds) == "d2b18b4f9f9f88189c82224ffa524e93"
+
+
+def test_format_for_logging():
+    ds = {"name": "care bears", "unit": "kilogram", "location": "GLO"}
+    answer = "{'location': 'GLO', 'name': 'care bears', 'unit': 'kilogram'}"
+    assert format_for_logging(ds) == answer
+
+
+def test_es2_activity_hash():
+    ds = ("foo", "bar")
+    assert es2_activity_hash(*ds) == "3858f62230ac3c915f300c664312c63f"
+    assert isinstance(es2_activity_hash(*ds), str)
+
+    ds = ("正しい馬", "バッテリーの定番")
+    assert es2_activity_hash(*ds) == "008e9536b44699d8b0d631d9acd76515"
+
+
+def test_standardize_method_to_len_3():
+    a = ("foo", "bar")
+    b = ()
+    c = tuple("abcde")
+    d = tuple("abc")
+    e = list("ab")
+    f = list("abcde")
+
+    assert standardize_method_to_len_3(a) == ("foo", "bar", "--")
+    assert standardize_method_to_len_3(a, "##") == ("foo", "bar", "##")
+    assert standardize_method_to_len_3(b) == ("--", "--", "--")
+    assert standardize_method_to_len_3(c) == ("a", "b", "c,d,e")
+    assert standardize_method_to_len_3(c, joiner="; ") == ("a", "b", "c; d; e")
+    assert standardize_method_to_len_3(d) == ("a", "b", "c")
+    assert standardize_method_to_len_3(e) == ("a", "b", "--")
+    assert standardize_method_to_len_3(f) == ("a", "b", "c,d,e")
+
+
+def test_rescale_exchange_nonnumber():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+    }
+    with pytest.raises(ValueError):
+        rescale_exchange(given, True)
+    with pytest.raises(ValueError):
+        rescale_exchange(given, "forty two")
+
+
+def test_rescale_exchange_unsupported_exchange():
+    given = {
+        "uncertainty type": 999,
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+    }
+    with pytest.raises(UnsupportedExchange):
+        rescale_exchange(given, 7.14)
+
+
+def test_rescale_exchange_zero():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+    }
+    expected = {"amount": 0.0, "loc": 0.0, "uncertainty type": UndefinedUncertainty.id}
+    assert rescale_exchange(given, 0) == expected
+
+
+def test_rescale_exchange_formula():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+        "formula": "foo * 7 + bar",
+    }
+    assert rescale_exchange(given, 3)["formula"] == "(foo * 7 + bar) * 3"
+
+
+def test_rescale_exchange_distribution_not_given():
+    given = {
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+    }
+    expected = {
+        "minimum": 0,
+        "maximum": 20,
+        "amount": 4,
+        "loc": 4,
+    }
+    assert rescale_exchange(given, 2) == expected
+
+
+def test_rescale_exchange_no_uncertainty():
+    given = {
+        "uncertainty type": NoUncertainty.id,
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+    }
+    expected = {
+        "uncertainty type": NoUncertainty.id,
+        "minimum": 0,
+        "maximum": 20,
+        "amount": 4,
+        "loc": 4,
+    }
+    assert rescale_exchange(given, 2) == expected
+
+
+def test_rescale_exchange_flip_min_max():
+    given = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": 0,
+        "maximum": 10,
+        "amount": 2,
+    }
+    expected = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": -20.0,
+        "maximum": 0.0,
+        "amount": -4.0,
+        "loc": -4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_min_to_max():
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "minimum": 1,
+        "amount": 2,
+    }
+    expected = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 1.0,
+        "maximum": -2.0,
+        "amount": -4.0,
+        "loc": -4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "minimum": -10,
+        "amount": -2,
+    }
+    expected = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 1.0,
+        "maximum": 20.0,
+        "amount": 4.0,
+        "loc": 4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_max_to_min():
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "maximum": 10,
+        "amount": 2,
+    }
+    expected = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 1.0,
+        "minimum": -20.0,
+        "amount": -4.0,
+        "loc": -4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "maximum": -1,
+        "amount": -2,
+    }
+    expected = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 1.0,
+        "minimum": 2.0,
+        "amount": 4.0,
+        "loc": 4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_normal_distribution():
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "amount": -2,
+    }
+    expected = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 1.0,
+        "amount": 4.0,
+        "loc": 4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_normal_distribution_scale_always_positive():
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "amount": 2,
+    }
+    expected = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 1.0,
+        "amount": -4.0,
+        "loc": -4.0,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_lognormal_distribution():
+    given = {
+        "uncertainty type": LognormalUncertainty.id,
+        "scale": 0.5,
+        "amount": -2,
+        "negative": True,
+    }
+    expected = {
+        "uncertainty type": LognormalUncertainty.id,
+        "scale": 0.5,
+        "amount": 4.0,
+        "loc": math.log(4),
+        "negative": False,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_lognormal_distribution_turn_negative():
+    given = {
+        "uncertainty type": LognormalUncertainty.id,
+        "scale": 0.5,
+        "amount": 2,
+    }
+    expected = {
+        "uncertainty type": LognormalUncertainty.id,
+        "scale": 0.5,
+        "amount": -4.0,
+        "loc": math.log(4),
+        "negative": True,
+    }
+    assert rescale_exchange(given, -2) == expected
+
+
+def test_rescale_exchange_remove_negative():
+    given = {
+        "uncertainty type": NormalUncertainty.id,
+        "scale": 0.5,
+        "amount": 2,
+        "negative": True,
+    }
+    assert "negative" not in rescale_exchange(given, -2)
+
+
+def test_rescale_exchange_triangular():
+    given = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": 1,
+        "amount": 2,
+        "maximum": 3,
+    }
+    expected = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": 10,
+        "amount": 20,
+        "loc": 20,
+        "maximum": 30,
+    }
+    assert rescale_exchange(given, 10) == expected
+
+
+def test_rescale_exchange_triangular_flip_sign_from_positive():
+    given = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": 1,
+        "amount": 2,
+        "maximum": 3,
+    }
+    expected = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": -30,
+        "amount": -20,
+        "loc": -20,
+        "maximum": -10,
+    }
+    assert rescale_exchange(given, -10) == expected
+
+
+def test_rescale_exchange_triangular_flip_sign_from_negative():
+    given = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": -3,
+        "amount": -2,
+        "maximum": -1,
+    }
+    expected = {
+        "uncertainty type": TriangularUncertainty.id,
+        "minimum": 10,
+        "amount": 20,
+        "loc": 20,
+        "maximum": 30,
+    }
+    assert rescale_exchange(given, -10) == expected
+
+
+def test_rescale_exchange_uniform():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 1,
+        "amount": 2,
+        "maximum": 3,
+    }
+    expected = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 10,
+        "amount": 20,
+        "loc": 20,
+        "maximum": 30,
+    }
+    assert rescale_exchange(given, 10) == expected
+
+
+def test_rescale_exchange_uniform_flip_sign_from_positive():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 1,
+        "amount": 2,
+        "maximum": 3,
+    }
+    expected = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": -30,
+        "amount": -20,
+        "loc": -20,
+        "maximum": -10,
+    }
+    assert rescale_exchange(given, -10) == expected
+
+
+def test_rescale_exchange_uniform_flip_sign_from_negative():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": -3,
+        "amount": -2.5,
+        "maximum": -1,
+    }
+    expected = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 10,
+        "amount": 25,
+        "loc": 25,
+        "maximum": 30,
+    }
+    assert rescale_exchange(given, -10) == expected
+
+
+def test_rescale_exchange_uniform_no_amount():
+    given = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 1,
+        "maximum": 3,
+    }
+    expected = {
+        "uncertainty type": UniformUncertainty.id,
+        "minimum": 10,
+        "amount": 20,
+        "loc": 20,
+        "maximum": 30,
+    }
+    assert rescale_exchange(given, 10) == expected
diff --git a/tests/utils.py b/tests/utils.py
deleted file mode 100644
index 30b784f..0000000
--- a/tests/utils.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import sys
-import unittest
-
-from bw2io.utils import (
-    activity_hash,
-    es2_activity_hash,
-    format_for_logging,
-    load_json_data_file,
-    standardize_method_to_len_3,
-)
-
-
-class UtilsTestCase(unittest.TestCase):
-    def test_load_json_data(self):
-        self.assertEqual({"1": {"2": 3}}, load_json_data_file("test"))
-        self.assertEqual({"1": {"2": 3}}, load_json_data_file("test.json"))
-
-    def test_activity_hash(self):
-        self.assertEqual(activity_hash({}), "d41d8cd98f00b204e9800998ecf8427e")
-        self.assertEqual(activity_hash({}), "d41d8cd98f00b204e9800998ecf8427e")
-        self.assertTrue(isinstance(activity_hash({}), str))
-        ds = {"name": "care bears", "unit": "kilogram", "location": "GLO"}
-        self.assertEqual(activity_hash(ds), "a6d6dd46cc33acd23826fa5b4e83377f")
-        ds = {
-            "name": "care bears",
-            "categories": ["toys", "fun"],
-            "unit": "kilogram",
-            "reference product": "lollipops",
-            "location": "GLO",
-            "extra": "irrelevant",
-        }
-        self.assertEqual(activity_hash(ds), "90d8689ec08dceb9507d28a36df951cd")
-        ds = {"name": "正しい馬のバッテリーの定番"}
-        self.assertEqual(activity_hash(ds), "d2b18b4f9f9f88189c82224ffa524e93")
-
-    def test_format_for_logging(self):
-        ds = {"name": "care bears", "unit": "kilogram", "location": "GLO"}
-        if sys.version_info < (3, 0):
-            answer = (
-                b"{ u'location': u'GLO', u'name': u'care bears', u'unit': u'kilogram'}"
-            )
-        else:
-            answer = "{'location': 'GLO', 'name': 'care bears', 'unit': 'kilogram'}"
-        self.assertEqual(format_for_logging(ds), answer)
-
-    def test_es2_activity_hash(self):
-        ds = ("foo", "bar")
-        self.assertEqual(es2_activity_hash(*ds), "3858f62230ac3c915f300c664312c63f")
-        self.assertTrue(isinstance(es2_activity_hash(*ds), str))
-        ds = ("正しい馬", "バッテリーの定番")
-        self.assertEqual(es2_activity_hash(*ds), "008e9536b44699d8b0d631d9acd76515")
-
-
-def test_standardize_method_to_len_3():
-    a = ("foo", "bar")
-    b = ()
-    c = tuple("abcde")
-    d = tuple("abc")
-    e = list("ab")
-    f = list("abcde")
-
-    assert standardize_method_to_len_3(a) == ("foo", "bar", "--")
-    assert standardize_method_to_len_3(a, "##") == ("foo", "bar", "##")
-    assert standardize_method_to_len_3(b) == ("--", "--", "--")
-    assert standardize_method_to_len_3(c) == ("a", "b", "c,d,e")
-    assert standardize_method_to_len_3(c, joiner="; ") == ("a", "b", "c; d; e")
-    assert standardize_method_to_len_3(d) == ("a", "b", "c")
-    assert standardize_method_to_len_3(e) == ("a", "b", "--")
-    assert standardize_method_to_len_3(f) == ("a", "b", "c,d,e")