From d50f99a406f2982813aeb97e7334da8fe787ec41 Mon Sep 17 00:00:00 2001 From: stagegrowth Date: Wed, 7 Jan 2026 15:19:26 +0900 Subject: [PATCH 1/2] api: Summarize EquivalentAddressGroup toString --- .../java/io/grpc/EquivalentAddressGroup.java | 21 ++++- .../test/java/io/grpc/CallOptionsTest.java | 54 +++++++---- .../io/grpc/EquivalentAddressGroupTest.java | 92 +++++++++++++++++++ 3 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 api/src/test/java/io/grpc/EquivalentAddressGroupTest.java 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/CallOptionsTest.java b/api/src/test/java/io/grpc/CallOptionsTest.java index 65fb7ff3bf2..6cdc0f983f7 100644 --- a/api/src/test/java/io/grpc/CallOptionsTest.java +++ b/api/src/test/java/io/grpc/CallOptionsTest.java @@ -29,7 +29,9 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; -import com.google.common.base.Objects; +import com.google.common.truth.Fact; +import com.google.common.truth.FailureMetadata; +import com.google.common.truth.Subject; import io.grpc.ClientStreamTracer.StreamInfo; import io.grpc.internal.SerializingExecutor; import java.time.Duration; @@ -106,17 +108,15 @@ public void allWiths() { @Test public void noStrayModifications() { - assertThat(equal(allSet, allSet.withAuthority("blah").withAuthority(sampleAuthority))) - .isTrue(); - assertThat( - equal(allSet, - allSet.withDeadline(Deadline.after(314, NANOSECONDS)).withDeadline(sampleDeadline))) - .isTrue(); - assertThat( - equal(allSet, + assertAbout(callOptions()).that(allSet.withAuthority("blah").withAuthority(sampleAuthority)) + .isEquivalentTo(allSet); + assertAbout(callOptions()).that( + allSet.withDeadline(Deadline.after(314, NANOSECONDS)).withDeadline(sampleDeadline)) + .isEquivalentTo(allSet); + assertAbout(callOptions()).that( allSet.withCallCredentials(mock(CallCredentials.class)) - .withCallCredentials(sampleCreds))) - .isTrue(); + .withCallCredentials(sampleCreds)) + .isEquivalentTo(allSet); } @Test @@ -265,12 +265,32 @@ public void getWaitForReady() { assertSame(CallOptions.DEFAULT.withoutWaitForReady().getWaitForReady(), Boolean.FALSE); } - // Only used in noStrayModifications() - // TODO(carl-mastrangelo): consider making a CallOptionsSubject for Truth. - private static boolean equal(CallOptions o1, CallOptions o2) { - return Objects.equal(o1.getDeadline(), o2.getDeadline()) - && Objects.equal(o1.getAuthority(), o2.getAuthority()) - && Objects.equal(o1.getCredentials(), o2.getCredentials()); + private static Subject.Factory callOptions() { + return CallOptionsSubject::new; + } + + private static final class CallOptionsSubject extends Subject { + + private final CallOptions actual; + + private CallOptionsSubject(FailureMetadata metadata, CallOptions actual) { + super(metadata, actual); + this.actual = actual; + } + + public void isEquivalentTo(CallOptions expected) { + if (actual == null) { + failWithActual("expected", expected); + return; + } + if (expected == null) { + failWithoutActual(Fact.simpleFact("expected non-null CallOptions")); + return; + } + check("deadline").that(actual.getDeadline()).isEqualTo(expected.getDeadline()); + check("authority").that(actual.getAuthority()).isEqualTo(expected.getAuthority()); + check("credentials").that(actual.getCredentials()).isEqualTo(expected.getCredentials()); + } } private static class FakeTicker extends Deadline.Ticker { 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; + } + } +} From 66c5e9e9b34c475ff2f2503f70469e5ea921e81a Mon Sep 17 00:00:00 2001 From: stagegrowth Date: Thu, 8 Jan 2026 08:10:47 +0900 Subject: [PATCH 2/2] api: Revert CallOptionsTest refactor from this PR --- .../test/java/io/grpc/CallOptionsTest.java | 54 ++++++------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/api/src/test/java/io/grpc/CallOptionsTest.java b/api/src/test/java/io/grpc/CallOptionsTest.java index 6cdc0f983f7..65fb7ff3bf2 100644 --- a/api/src/test/java/io/grpc/CallOptionsTest.java +++ b/api/src/test/java/io/grpc/CallOptionsTest.java @@ -29,9 +29,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; -import com.google.common.truth.Fact; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; +import com.google.common.base.Objects; import io.grpc.ClientStreamTracer.StreamInfo; import io.grpc.internal.SerializingExecutor; import java.time.Duration; @@ -108,15 +106,17 @@ public void allWiths() { @Test public void noStrayModifications() { - assertAbout(callOptions()).that(allSet.withAuthority("blah").withAuthority(sampleAuthority)) - .isEquivalentTo(allSet); - assertAbout(callOptions()).that( - allSet.withDeadline(Deadline.after(314, NANOSECONDS)).withDeadline(sampleDeadline)) - .isEquivalentTo(allSet); - assertAbout(callOptions()).that( + assertThat(equal(allSet, allSet.withAuthority("blah").withAuthority(sampleAuthority))) + .isTrue(); + assertThat( + equal(allSet, + allSet.withDeadline(Deadline.after(314, NANOSECONDS)).withDeadline(sampleDeadline))) + .isTrue(); + assertThat( + equal(allSet, allSet.withCallCredentials(mock(CallCredentials.class)) - .withCallCredentials(sampleCreds)) - .isEquivalentTo(allSet); + .withCallCredentials(sampleCreds))) + .isTrue(); } @Test @@ -265,32 +265,12 @@ public void getWaitForReady() { assertSame(CallOptions.DEFAULT.withoutWaitForReady().getWaitForReady(), Boolean.FALSE); } - private static Subject.Factory callOptions() { - return CallOptionsSubject::new; - } - - private static final class CallOptionsSubject extends Subject { - - private final CallOptions actual; - - private CallOptionsSubject(FailureMetadata metadata, CallOptions actual) { - super(metadata, actual); - this.actual = actual; - } - - public void isEquivalentTo(CallOptions expected) { - if (actual == null) { - failWithActual("expected", expected); - return; - } - if (expected == null) { - failWithoutActual(Fact.simpleFact("expected non-null CallOptions")); - return; - } - check("deadline").that(actual.getDeadline()).isEqualTo(expected.getDeadline()); - check("authority").that(actual.getAuthority()).isEqualTo(expected.getAuthority()); - check("credentials").that(actual.getCredentials()).isEqualTo(expected.getCredentials()); - } + // Only used in noStrayModifications() + // TODO(carl-mastrangelo): consider making a CallOptionsSubject for Truth. + private static boolean equal(CallOptions o1, CallOptions o2) { + return Objects.equal(o1.getDeadline(), o2.getDeadline()) + && Objects.equal(o1.getAuthority(), o2.getAuthority()) + && Objects.equal(o1.getCredentials(), o2.getCredentials()); } private static class FakeTicker extends Deadline.Ticker {