diff --git a/api/src/main/java/io/grpc/EquivalentAddressGroup.java b/api/src/main/java/io/grpc/EquivalentAddressGroup.java index bf8a864902c..a640052b0ab 100644 --- a/api/src/main/java/io/grpc/EquivalentAddressGroup.java +++ b/api/src/main/java/io/grpc/EquivalentAddressGroup.java @@ -34,6 +34,7 @@ */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770") public final class EquivalentAddressGroup { + private static final int MAX_ADDRESSES_TO_STRING = 100; /** * The authority to be used when constructing Subchannels for this EquivalentAddressGroup. @@ -113,8 +114,24 @@ public Attributes getAttributes() { @Override public String toString() { - // TODO(zpencer): Summarize return value if addr is very large - return "[" + addrs + "/" + attrs + "]"; + StringBuilder sb = new StringBuilder(); + sb.append('['); + if (addrs.size() <= MAX_ADDRESSES_TO_STRING) { + sb.append(addrs); + } else { + sb.append('['); + for (int i = 0; i < MAX_ADDRESSES_TO_STRING; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(addrs.get(i)); + } + sb.append(", ... "); + sb.append(addrs.size() - MAX_ADDRESSES_TO_STRING); + sb.append(" more]"); + } + sb.append('/').append(attrs).append(']'); + return sb.toString(); } @Override diff --git a/api/src/test/java/io/grpc/EquivalentAddressGroupTest.java b/api/src/test/java/io/grpc/EquivalentAddressGroupTest.java new file mode 100644 index 00000000000..2ea257fd35a --- /dev/null +++ b/api/src/test/java/io/grpc/EquivalentAddressGroupTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2026 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + +import static com.google.common.truth.Truth.assertThat; + +import java.lang.reflect.Field; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link EquivalentAddressGroup}. + */ +@RunWith(JUnit4.class) +public class EquivalentAddressGroupTest { + + @Test + public void toString_summarizesLargeAddressList() { + int maxAddressesToString = maxAddressesToString(); + List addrs = new ArrayList<>(); + for (int i = 0; i <= maxAddressesToString; i++) { + addrs.add(new FakeSocketAddress("addr" + i)); + } + EquivalentAddressGroup eag = new EquivalentAddressGroup(addrs); + + StringBuilder expected = new StringBuilder(); + expected.append('[').append('['); + for (int i = 0; i < maxAddressesToString; i++) { + if (i > 0) { + expected.append(", "); + } + expected.append(addrs.get(i)); + } + expected.append(", ... 1 more]/{}]"); + assertThat(eag.toString()).isEqualTo(expected.toString()); + } + + @Test + public void toString_doesNotSummarizeAtMaxAddressCount() { + int maxAddressesToString = maxAddressesToString(); + List addrs = new ArrayList<>(); + for (int i = 0; i < maxAddressesToString; i++) { + addrs.add(new FakeSocketAddress("addr" + i)); + } + EquivalentAddressGroup eag = new EquivalentAddressGroup(addrs); + + String expected = "[" + addrs + "/{}]"; + assertThat(eag.toString()).isEqualTo(expected); + } + + private static int maxAddressesToString() { + try { + Field field = EquivalentAddressGroup.class.getDeclaredField("MAX_ADDRESSES_TO_STRING"); + field.setAccessible(true); + return (int) field.get(null); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new LinkageError("Unable to read MAX_ADDRESSES_TO_STRING", e); + } + } + + private static final class FakeSocketAddress extends SocketAddress { + + private final String name; + + FakeSocketAddress(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } +}