Skip to content

Commit 95d46a5

Browse files
committed
test: add TestApiClock and TestScheduledExecutorService
Our retries will be dependant upon a ScheduledExecutorService and ApiClock to compute elapsed time and enqueue backoff operations. Define a TestApiClick and TestScheduledExecutorService that uses the TestApiClock that can advance arbitrary amounts of time "instantly". This also enables deterministic tests of scheduling
1 parent 413d65f commit 95d46a5

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import com.google.api.core.ApiClock;
20+
import java.time.Duration;
21+
import java.util.concurrent.TimeUnit;
22+
import java.util.function.LongUnaryOperator;
23+
import org.checkerframework.checker.nullness.qual.Nullable;
24+
25+
/** "Test" {@link ApiClock} that allows control of time advancement and by how much. */
26+
final class TestApiClock implements ApiClock {
27+
28+
private static final long NANOS_PER_MILLI = TimeUnit.MILLISECONDS.toNanos(1);
29+
private final long beginNs;
30+
private final LongUnaryOperator tick;
31+
32+
@Nullable private LongUnaryOperator next;
33+
private long prevNs;
34+
35+
private TestApiClock(long beginNs, LongUnaryOperator tick) {
36+
this.beginNs = beginNs;
37+
this.tick = tick;
38+
this.prevNs = beginNs;
39+
}
40+
41+
@Override
42+
public long nanoTime() {
43+
final long ret;
44+
if (next != null) {
45+
ret = next.applyAsLong(prevNs);
46+
next = null;
47+
} else {
48+
ret = tick.applyAsLong(prevNs);
49+
}
50+
prevNs = ret;
51+
return ret;
52+
}
53+
54+
@Override
55+
public long millisTime() {
56+
return nanoTime() / NANOS_PER_MILLI;
57+
}
58+
59+
public void advance(long nanos) {
60+
next = addExact(nanos);
61+
}
62+
63+
public void advance(Duration d) {
64+
advance(d.toNanos());
65+
}
66+
67+
public void reset() {
68+
prevNs = beginNs;
69+
next = null;
70+
}
71+
72+
public static TestApiClock tickBy(long begin, Duration d) {
73+
return of(begin, addExact(d.toNanos()));
74+
}
75+
76+
public static TestApiClock of() {
77+
return of(0L, addExact(1L));
78+
}
79+
80+
/** @param tick Given the previous nanoseconds of the clock generate the new nanoseconds */
81+
public static TestApiClock of(long beginNs, LongUnaryOperator tick) {
82+
return new TestApiClock(beginNs, tick);
83+
}
84+
85+
private static LongUnaryOperator addExact(long amountToAdd) {
86+
return l -> Math.addExact(l, amountToAdd);
87+
}
88+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage;
18+
19+
import java.time.Duration;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.concurrent.Callable;
23+
import java.util.concurrent.Delayed;
24+
import java.util.concurrent.ExecutionException;
25+
import java.util.concurrent.Future;
26+
import java.util.concurrent.ScheduledExecutorService;
27+
import java.util.concurrent.ScheduledFuture;
28+
import java.util.concurrent.TimeUnit;
29+
import java.util.concurrent.TimeoutException;
30+
31+
/**
32+
* "Test" {@link ScheduledExecutorService} that integrated with {@link TestApiClock} to provide
33+
* "instant" time advancement for any invocation of {@link #schedule(Runnable, long, TimeUnit)}
34+
*
35+
* <p>All other methods will throw {@link UnsupportedOperationException} if invoked.
36+
*/
37+
final class TestScheduledExecutorService implements ScheduledExecutorService {
38+
39+
private final TestApiClock clock;
40+
41+
TestScheduledExecutorService(TestApiClock clock) {
42+
this.clock = clock;
43+
}
44+
45+
@Override
46+
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
47+
Duration nanos = Duration.ofNanos(unit.toNanos(delay));
48+
clock.advance(nanos);
49+
command.run();
50+
return new ScheduledFuture<Object>() {
51+
@Override
52+
public long getDelay(TimeUnit unit) {
53+
throw new UnsupportedOperationException();
54+
}
55+
56+
@Override
57+
public int compareTo(Delayed o) {
58+
throw new UnsupportedOperationException();
59+
}
60+
61+
@Override
62+
public boolean cancel(boolean mayInterruptIfRunning) {
63+
throw new UnsupportedOperationException();
64+
}
65+
66+
@Override
67+
public boolean isCancelled() {
68+
throw new UnsupportedOperationException();
69+
}
70+
71+
@Override
72+
public boolean isDone() {
73+
return true;
74+
}
75+
76+
@Override
77+
public Object get() throws InterruptedException, ExecutionException {
78+
return null;
79+
}
80+
81+
@Override
82+
public Object get(long timeout, TimeUnit unit)
83+
throws InterruptedException, ExecutionException, TimeoutException {
84+
throw new UnsupportedOperationException();
85+
}
86+
};
87+
}
88+
89+
@Override
90+
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
91+
throw new UnsupportedOperationException();
92+
}
93+
94+
@Override
95+
public ScheduledFuture<?> scheduleAtFixedRate(
96+
Runnable command, long initialDelay, long period, TimeUnit unit) {
97+
throw new UnsupportedOperationException();
98+
}
99+
100+
@Override
101+
public ScheduledFuture<?> scheduleWithFixedDelay(
102+
Runnable command, long initialDelay, long delay, TimeUnit unit) {
103+
throw new UnsupportedOperationException();
104+
}
105+
106+
@Override
107+
public void shutdown() {
108+
throw new UnsupportedOperationException();
109+
}
110+
111+
@Override
112+
public List<Runnable> shutdownNow() {
113+
throw new UnsupportedOperationException();
114+
}
115+
116+
@Override
117+
public boolean isShutdown() {
118+
throw new UnsupportedOperationException();
119+
}
120+
121+
@Override
122+
public boolean isTerminated() {
123+
throw new UnsupportedOperationException();
124+
}
125+
126+
@Override
127+
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
128+
throw new UnsupportedOperationException();
129+
}
130+
131+
@Override
132+
public <T> Future<T> submit(Callable<T> task) {
133+
throw new UnsupportedOperationException();
134+
}
135+
136+
@Override
137+
public <T> Future<T> submit(Runnable task, T result) {
138+
throw new UnsupportedOperationException();
139+
}
140+
141+
@Override
142+
public Future<?> submit(Runnable task) {
143+
throw new UnsupportedOperationException();
144+
}
145+
146+
@Override
147+
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
148+
throws InterruptedException {
149+
throw new UnsupportedOperationException();
150+
}
151+
152+
@Override
153+
public <T> List<Future<T>> invokeAll(
154+
Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
155+
throws InterruptedException {
156+
throw new UnsupportedOperationException();
157+
}
158+
159+
@Override
160+
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
161+
throws InterruptedException, ExecutionException {
162+
throw new UnsupportedOperationException();
163+
}
164+
165+
@Override
166+
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
167+
throws InterruptedException, ExecutionException, TimeoutException {
168+
throw new UnsupportedOperationException();
169+
}
170+
171+
@Override
172+
public void execute(Runnable command) {
173+
throw new UnsupportedOperationException();
174+
}
175+
}

0 commit comments

Comments
 (0)