Repository: networkx/networkx

Issue:
Improve test coverage for multigraph class
I discovered there are still some red spots in this link https://app.codecov.io/gh/networkx/networkx/blob/main/networkx/classes/multigraph.py and will like to work on it. 

The tests will be added in https://github.com/networkx/networkx/blob/main/networkx/classes/tests/test_multigraph.py

Current Behavior
We don't test all the paths the code can take us.

Expected Behavior
We should be testing everything so there aren't any surprises.

Steps to Reproduce
Visit https://app.codecov.io/gh/networkx/networkx/blob/main//networkx/classes/multigraph.py


Comments:

Patch:
diff --git a/examples/drawing/plot_eigenvalues.py b/examples/drawing/plot_eigenvalues.py
index b0df67ae9..67322cfd1 100644
--- a/examples/drawing/plot_eigenvalues.py
+++ b/examples/drawing/plot_eigenvalues.py
@@ -14,7 +14,7 @@ m = 5000  # 5000 edges
 G = nx.gnm_random_graph(n, m, seed=5040)  # Seed for reproducibility
 
 L = nx.normalized_laplacian_matrix(G)
-e = numpy.linalg.eigvals(L.A)
+e = numpy.linalg.eigvals(L.toarray())
 print("Largest eigenvalue:", max(e))
 print("Smallest eigenvalue:", min(e))
 plt.hist(e, bins=100)  # histogram with 100 bins
diff --git a/networkx/algorithms/flow/maxflow.py b/networkx/algorithms/flow/maxflow.py
index 8d2fb8f03..b0098d0a0 100644
--- a/networkx/algorithms/flow/maxflow.py
+++ b/networkx/algorithms/flow/maxflow.py
@@ -12,14 +12,6 @@ from .utils import build_flow_dict
 
 # Define the default flow function for computing maximum flow.
 default_flow_func = preflow_push
-# Functions that don't support cutoff for minimum cut computations.
-flow_funcs = [
-    boykov_kolmogorov,
-    dinitz,
-    edmonds_karp,
-    preflow_push,
-    shortest_augmenting_path,
-]
 
 __all__ = ["maximum_flow", "maximum_flow_value", "minimum_cut", "minimum_cut_value"]
 
@@ -452,7 +444,7 @@ def minimum_cut(flowG, _s, _t, capacity="capacity", flow_func=None, **kwargs):
     if not callable(flow_func):
         raise nx.NetworkXError("flow_func has to be callable.")
 
-    if kwargs.get("cutoff") is not None and flow_func in flow_funcs:
+    if kwargs.get("cutoff") is not None and flow_func is preflow_push:
         raise nx.NetworkXError("cutoff should not be specified.")
 
     R = flow_func(flowG, _s, _t, capacity=capacity, value_only=True, **kwargs)
@@ -603,7 +595,7 @@ def minimum_cut_value(flowG, _s, _t, capacity="capacity", flow_func=None, **kwar
     if not callable(flow_func):
         raise nx.NetworkXError("flow_func has to be callable.")
 
-    if kwargs.get("cutoff") is not None and flow_func in flow_funcs:
+    if kwargs.get("cutoff") is not None and flow_func is preflow_push:
         raise nx.NetworkXError("cutoff should not be specified.")
 
     R = flow_func(flowG, _s, _t, capacity=capacity, value_only=True, **kwargs)
diff --git a/networkx/readwrite/gml.py b/networkx/readwrite/gml.py
index da5ccca58..758c0c73b 100644
--- a/networkx/readwrite/gml.py
+++ b/networkx/readwrite/gml.py
@@ -363,6 +363,11 @@ def parse_gml_lines(lines, label, destringizer):
                         value = destringizer(value)
                     except ValueError:
                         pass
+                # Special handling for empty lists and tuples
+                if value == "()":
+                    value = tuple()
+                if value == "[]":
+                    value = list()
                 curr_token = next(tokens)
             elif category == Pattern.DICT_START:
                 curr_token, value = parse_dict(curr_token)
@@ -728,12 +733,9 @@ def generate_gml(G, stringizer=None):
                 for key, value in value.items():
                     yield from stringize(key, value, (), next_indent)
                 yield indent + "]"
-            elif (
-                isinstance(value, (list, tuple))
-                and key != "label"
-                and value
-                and not in_list
-            ):
+            elif isinstance(value, (list, tuple)) and key != "label" and not in_list:
+                if len(value) == 0:
+                    yield indent + key + " " + f'"{value!r}"'
                 if len(value) == 1:
                     yield indent + key + " " + f'"{LIST_START_VALUE}"'
                 for val in value:


Test patch:
diff --git a/networkx/algorithms/flow/tests/test_maxflow.py b/networkx/algorithms/flow/tests/test_maxflow.py
index 36bc5ec7c..d4f9052ce 100644
--- a/networkx/algorithms/flow/tests/test_maxflow.py
+++ b/networkx/algorithms/flow/tests/test_maxflow.py
@@ -20,6 +20,7 @@ flow_funcs = {
     preflow_push,
     shortest_augmenting_path,
 }
+
 max_min_funcs = {nx.maximum_flow, nx.minimum_cut}
 flow_value_funcs = {nx.maximum_flow_value, nx.minimum_cut_value}
 interface_funcs = max_min_funcs & flow_value_funcs
@@ -427,25 +428,24 @@ class TestMaxFlowMinCutInterface:
 
     def test_minimum_cut_no_cutoff(self):
         G = self.G
-        for flow_func in flow_funcs:
-            pytest.raises(
-                nx.NetworkXError,
-                nx.minimum_cut,
-                G,
-                "x",
-                "y",
-                flow_func=flow_func,
-                cutoff=1.0,
-            )
-            pytest.raises(
-                nx.NetworkXError,
-                nx.minimum_cut_value,
-                G,
-                "x",
-                "y",
-                flow_func=flow_func,
-                cutoff=1.0,
-            )
+        pytest.raises(
+            nx.NetworkXError,
+            nx.minimum_cut,
+            G,
+            "x",
+            "y",
+            flow_func=preflow_push,
+            cutoff=1.0,
+        )
+        pytest.raises(
+            nx.NetworkXError,
+            nx.minimum_cut_value,
+            G,
+            "x",
+            "y",
+            flow_func=preflow_push,
+            cutoff=1.0,
+        )
 
     def test_kwargs(self):
         G = self.H
@@ -539,11 +539,20 @@ class TestCutoff:
         assert k <= R.graph["flow_value"] <= (2 * k)
         R = edmonds_karp(G, "s", "t", cutoff=k)
         assert k <= R.graph["flow_value"] <= (2 * k)
+        R = dinitz(G, "s", "t", cutoff=k)
+        assert k <= R.graph["flow_value"] <= (2 * k)
+        R = boykov_kolmogorov(G, "s", "t", cutoff=k)
+        assert k <= R.graph["flow_value"] <= (2 * k)
 
     def test_complete_graph_cutoff(self):
         G = nx.complete_graph(5)
         nx.set_edge_attributes(G, {(u, v): 1 for u, v in G.edges()}, "capacity")
-        for flow_func in [shortest_augmenting_path, edmonds_karp]:
+        for flow_func in [
+            shortest_augmenting_path,
+            edmonds_karp,
+            dinitz,
+            boykov_kolmogorov,
+        ]:
             for cutoff in [3, 2, 1]:
                 result = nx.maximum_flow_value(
                     G, 0, 4, flow_func=flow_func, cutoff=cutoff
diff --git a/networkx/algorithms/tests/test_richclub.py b/networkx/algorithms/tests/test_richclub.py
index 1ed42d459..5638ddbf0 100644
--- a/networkx/algorithms/tests/test_richclub.py
+++ b/networkx/algorithms/tests/test_richclub.py
@@ -80,6 +80,17 @@ def test_rich_club_exception2():
         nx.rich_club_coefficient(G)
 
 
+def test_rich_club_selfloop():
+    G = nx.Graph()  # or DiGraph, MultiGraph, MultiDiGraph, etc
+    G.add_edge(1, 1)  # self loop
+    G.add_edge(1, 2)
+    with pytest.raises(
+        Exception,
+        match="rich_club_coefficient is not implemented for " "graphs with self loops.",
+    ):
+        nx.rich_club_coefficient(G)
+
+
 # def test_richclub2_normalized():
 #    T = nx.balanced_tree(2,10)
 #    rcNorm = nx.richclub.rich_club_coefficient(T,Q=2)
diff --git a/networkx/classes/tests/test_multigraph.py b/networkx/classes/tests/test_multigraph.py
index 0584f5620..07f7f5db6 100644
--- a/networkx/classes/tests/test_multigraph.py
+++ b/networkx/classes/tests/test_multigraph.py
@@ -303,6 +303,9 @@ class TestMultiGraph(BaseMultiGraphTester, _TestGraph):
         G = self.Graph()
         G.add_edge(*(0, 1))
         assert G.adj == {0: {1: {0: {}}}, 1: {0: {0: {}}}}
+        G = self.Graph()
+        with pytest.raises(ValueError):
+            G.add_edge(None, "anything")
 
     def test_add_edge_conflicting_key(self):
         G = self.Graph()
diff --git a/networkx/readwrite/tests/test_gml.py b/networkx/readwrite/tests/test_gml.py
index 6e6ce0f3a..68b0da6f8 100644
--- a/networkx/readwrite/tests/test_gml.py
+++ b/networkx/readwrite/tests/test_gml.py
@@ -571,10 +571,6 @@ graph
         G = nx.Graph()
         G.graph["data"] = frozenset([1, 2, 3])
         assert_generate_error(G, stringizer=literal_stringizer)
-        G = nx.Graph()
-        G.graph["data"] = []
-        assert_generate_error(G)
-        assert_generate_error(G, stringizer=len)
 
     def test_label_kwarg(self):
         G = nx.parse_gml(self.simple_data, label="id")
@@ -712,3 +708,23 @@ class TestPropertyLists:
             f.seek(0)
             graph = nx.read_gml(f)
         assert graph.nodes(data=True)["n1"] == {"properties": ["element"]}
+
+
+@pytest.mark.parametrize("coll", (list(), tuple()))
+def test_stringize_empty_list_tuple(coll):
+    G = nx.path_graph(2)
+    G.nodes[0]["test"] = coll  # test serializing an empty collection
+    f = io.BytesIO()
+    nx.write_gml(G, f)  # Smoke test - should not raise
+    f.seek(0)
+    H = nx.read_gml(f)
+    assert H.nodes["0"]["test"] == coll  # Check empty list round-trips properly
+    # Check full round-tripping. Note that nodes are loaded as strings by
+    # default, so there needs to be some remapping prior to comparison
+    H = nx.relabel_nodes(H, {"0": 0, "1": 1})
+    assert nx.utils.graphs_equal(G, H)
+    # Same as above, but use destringizer for node remapping. Should have no
+    # effect on node attr
+    f.seek(0)
+    H = nx.read_gml(f, destringizer=int)
+    assert nx.utils.graphs_equal(G, H)