Skip to content

Commit 7af41a0

Browse files
author
VinayGuthal
authored
add uploader integration test (#381)
* add uploader integration test * address comments * address comments * address comments * update * comments * update uploader test
1 parent ad8fd39 commit 7af41a0

File tree

12 files changed

+392
-19
lines changed

12 files changed

+392
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime;
16+
17+
import com.google.android.datatransport.runtime.scheduling.DefaultScheduler;
18+
import com.google.android.datatransport.runtime.scheduling.Scheduler;
19+
import dagger.Binds;
20+
import dagger.Module;
21+
22+
@Module
23+
abstract class TestSchedulingModule {
24+
25+
@Binds
26+
abstract Scheduler scheduler(DefaultScheduler scheduler);
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime;
16+
17+
import android.content.Context;
18+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
19+
20+
public class TestWorkScheduler implements WorkScheduler {
21+
22+
private final Context context;
23+
24+
public TestWorkScheduler(Context applicationContext) {
25+
this.context = applicationContext;
26+
}
27+
28+
@Override
29+
public void schedule(TransportContext transportContext, int attemptNumber) {
30+
if (attemptNumber > 2) {
31+
return;
32+
}
33+
TransportRuntime.initialize(context);
34+
TransportRuntime.getInstance()
35+
.getUploader()
36+
.upload(transportContext.getBackendName(), attemptNumber, () -> {});
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static org.mockito.ArgumentMatchers.any;
19+
import static org.mockito.ArgumentMatchers.eq;
20+
import static org.mockito.Mockito.doThrow;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.spy;
23+
import static org.mockito.Mockito.times;
24+
import static org.mockito.Mockito.verify;
25+
import static org.mockito.Mockito.when;
26+
27+
import android.content.Context;
28+
import android.support.test.InstrumentationRegistry;
29+
import android.support.test.runner.AndroidJUnit4;
30+
import com.google.android.datatransport.Event;
31+
import com.google.android.datatransport.Priority;
32+
import com.google.android.datatransport.Transport;
33+
import com.google.android.datatransport.TransportFactory;
34+
import com.google.android.datatransport.runtime.backends.BackendRegistry;
35+
import com.google.android.datatransport.runtime.backends.BackendRequest;
36+
import com.google.android.datatransport.runtime.backends.BackendResponse;
37+
import com.google.android.datatransport.runtime.backends.TransportBackend;
38+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
39+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
40+
import com.google.android.datatransport.runtime.scheduling.persistence.PersistedEvent;
41+
import com.google.android.datatransport.runtime.synchronization.SynchronizationException;
42+
import java.util.Collections;
43+
import java.util.UUID;
44+
import org.junit.Before;
45+
import org.junit.Rule;
46+
import org.junit.Test;
47+
import org.junit.runner.RunWith;
48+
import org.mockito.stubbing.Answer;
49+
50+
@RunWith(AndroidJUnit4.class)
51+
public class UploaderIntegrationTest {
52+
private static final String testTransport = "testTransport";
53+
private final TransportBackend mockBackend = mock(TransportBackend.class);
54+
private final BackendRegistry mockRegistry = mock(BackendRegistry.class);
55+
private final Context context = InstrumentationRegistry.getInstrumentation().getContext();
56+
private final WorkScheduler spyScheduler = spy(new TestWorkScheduler(context));
57+
58+
private final TransportRuntimeComponent component =
59+
DaggerUploaderTestRuntimeComponent.builder()
60+
.setApplicationContext(context)
61+
.setBackendRegistry(mockRegistry)
62+
.setWorkScheduler(spyScheduler)
63+
.setEventClock(() -> 3)
64+
.setUptimeClock(() -> 1)
65+
.build();
66+
67+
@Rule public final TransportRuntimeRule runtimeRule = new TransportRuntimeRule(component);
68+
69+
@Before
70+
public void setUp() {
71+
when(mockBackend.decorate(any()))
72+
.thenAnswer(
73+
(Answer<EventInternal>)
74+
invocation -> invocation.<EventInternal>getArgument(0).toBuilder().build());
75+
}
76+
77+
private String generateBackendName() {
78+
return UUID.randomUUID().toString().replace("-", "");
79+
}
80+
81+
@Test
82+
public void uploader_transientError_shouldReschedule() {
83+
TransportRuntime runtime = TransportRuntime.getInstance();
84+
EventStore store = component.getEventStore();
85+
String mockBackendName = generateBackendName();
86+
TransportContext transportContext =
87+
TransportContext.builder()
88+
.setBackendName(mockBackendName)
89+
.setPriority(Priority.DEFAULT)
90+
.build();
91+
when(mockRegistry.get(mockBackendName)).thenReturn(mockBackend);
92+
when(mockBackend.send(any())).thenReturn(BackendResponse.transientError());
93+
TransportFactory factory = runtime.newFactory(mockBackendName);
94+
Transport<String> transport =
95+
factory.getTransport(testTransport, String.class, String::getBytes);
96+
Event<String> stringEvent = Event.ofTelemetry("TelemetryData");
97+
EventInternal expectedEvent =
98+
EventInternal.builder()
99+
.setEventMillis(3)
100+
.setUptimeMillis(1)
101+
.setTransportName(testTransport)
102+
.setPayload("TelemetryData".getBytes())
103+
.build();
104+
transport.send(stringEvent);
105+
verify(mockBackend, times(2))
106+
.send(eq(BackendRequest.create(Collections.singletonList(expectedEvent))));
107+
verify(spyScheduler, times(1)).schedule(any(), eq(2));
108+
Iterable<PersistedEvent> eventList = store.loadBatch(transportContext);
109+
assertThat(eventList).isNotEmpty();
110+
for (PersistedEvent persistedEvent : eventList) {
111+
assertThat(persistedEvent.getEvent()).isEqualTo(expectedEvent);
112+
}
113+
114+
assertThat(store.getNextCallTime(transportContext)).isEqualTo(0);
115+
}
116+
117+
@Test
118+
public void uploader_ok_shouldNotReschedule() {
119+
TransportRuntime runtime = TransportRuntime.getInstance();
120+
EventStore store = component.getEventStore();
121+
String mockBackendName = generateBackendName();
122+
TransportContext transportContext =
123+
TransportContext.builder()
124+
.setBackendName(mockBackendName)
125+
.setPriority(Priority.DEFAULT)
126+
.build();
127+
when(mockRegistry.get(mockBackendName)).thenReturn(mockBackend);
128+
when(mockBackend.send(any())).thenReturn(BackendResponse.ok(1000));
129+
TransportFactory factory = runtime.newFactory(mockBackendName);
130+
Transport<String> transport =
131+
factory.getTransport(testTransport, String.class, String::getBytes);
132+
Event<String> stringEvent = Event.ofTelemetry("TelemetryData");
133+
EventInternal expectedEvent =
134+
EventInternal.builder()
135+
.setEventMillis(3)
136+
.setUptimeMillis(1)
137+
.setTransportName(testTransport)
138+
.setPayload("TelemetryData".getBytes())
139+
.build();
140+
transport.send(stringEvent);
141+
verify(mockBackend, times(1))
142+
.send(eq(BackendRequest.create(Collections.singletonList(expectedEvent))));
143+
verify(spyScheduler, times(0)).schedule(any(), eq(2));
144+
assertThat(store.loadBatch(transportContext)).isEmpty();
145+
assertThat(store.getNextCallTime(transportContext)).isAtLeast((long) 1000);
146+
}
147+
148+
@Test
149+
public void uploader_nonTransientError_shouldNotReschedule() {
150+
TransportRuntime runtime = TransportRuntime.getInstance();
151+
EventStore store = component.getEventStore();
152+
String mockBackendName = generateBackendName();
153+
TransportContext transportContext =
154+
TransportContext.builder()
155+
.setBackendName(mockBackendName)
156+
.setPriority(Priority.DEFAULT)
157+
.build();
158+
when(mockRegistry.get(mockBackendName)).thenReturn(mockBackend);
159+
when(mockBackend.send(any())).thenReturn(BackendResponse.fatalError());
160+
TransportFactory factory = runtime.newFactory(mockBackendName);
161+
Transport<String> transport =
162+
factory.getTransport(testTransport, String.class, String::getBytes);
163+
Event<String> stringEvent = Event.ofTelemetry("TelemetryData");
164+
EventInternal expectedEvent =
165+
EventInternal.builder()
166+
.setEventMillis(3)
167+
.setUptimeMillis(1)
168+
.setTransportName(testTransport)
169+
.setPayload("TelemetryData".getBytes())
170+
.build();
171+
transport.send(stringEvent);
172+
verify(mockBackend, times(1))
173+
.send(eq(BackendRequest.create(Collections.singletonList(expectedEvent))));
174+
verify(spyScheduler, times(0)).schedule(any(), eq(2));
175+
assertThat(store.loadBatch(transportContext)).isEmpty();
176+
assertThat(store.getNextCallTime(transportContext)).isEqualTo(0);
177+
}
178+
179+
@Test
180+
public void uploader_dbException_shouldReschedule() {
181+
TransportRuntime runtime = TransportRuntime.getInstance();
182+
EventStore store = component.getEventStore();
183+
String mockBackendName = generateBackendName();
184+
185+
TransportContext transportContext =
186+
TransportContext.builder()
187+
.setBackendName(mockBackendName)
188+
.setPriority(Priority.DEFAULT)
189+
.build();
190+
when(mockRegistry.get(mockBackendName)).thenReturn(mockBackend);
191+
doThrow(new SynchronizationException("Error", null)).when(store).loadBatch(any());
192+
TransportFactory factory = runtime.newFactory(mockBackendName);
193+
Transport<String> transport =
194+
factory.getTransport(testTransport, String.class, String::getBytes);
195+
Event<String> stringEvent = Event.ofTelemetry("TelemetryData");
196+
EventInternal expectedEvent =
197+
EventInternal.builder()
198+
.setEventMillis(3)
199+
.setUptimeMillis(1)
200+
.setTransportName(testTransport)
201+
.setPayload("TelemetryData".getBytes())
202+
.build();
203+
transport.send(stringEvent);
204+
verify(spyScheduler, times(1)).schedule(any(), eq(2));
205+
assertThat(store.getNextCallTime(transportContext)).isEqualTo(0);
206+
}
207+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime;
16+
17+
import android.content.Context;
18+
import com.google.android.datatransport.runtime.backends.BackendRegistry;
19+
import com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkScheduler;
20+
import com.google.android.datatransport.runtime.scheduling.persistence.EventStore;
21+
import com.google.android.datatransport.runtime.scheduling.persistence.SpyEventStoreModule;
22+
import com.google.android.datatransport.runtime.time.Clock;
23+
import com.google.android.datatransport.runtime.time.Monotonic;
24+
import com.google.android.datatransport.runtime.time.WallTime;
25+
import dagger.BindsInstance;
26+
import dagger.Component;
27+
import javax.inject.Singleton;
28+
29+
@Component(
30+
modules = {
31+
SpyEventStoreModule.class,
32+
TestExecutionModule.class,
33+
TestSchedulingModule.class,
34+
})
35+
@Singleton
36+
abstract class UploaderTestRuntimeComponent extends TransportRuntimeComponent {
37+
38+
abstract TransportRuntime getTransportRuntime();
39+
40+
abstract EventStore getEventStore();
41+
42+
@Component.Builder
43+
interface Builder {
44+
@BindsInstance
45+
Builder setApplicationContext(Context applicationContext);
46+
47+
@BindsInstance
48+
Builder setWorkScheduler(WorkScheduler workScheduler);
49+
50+
@BindsInstance
51+
Builder setBackendRegistry(BackendRegistry registry);
52+
53+
@BindsInstance
54+
Builder setUptimeClock(@Monotonic Clock clock);
55+
56+
@BindsInstance
57+
Builder setEventClock(@WallTime Clock clock);
58+
59+
UploaderTestRuntimeComponent build();
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime.scheduling.persistence;
16+
17+
import static org.mockito.Mockito.spy;
18+
19+
import com.google.android.datatransport.runtime.synchronization.SynchronizationGuard;
20+
import dagger.Binds;
21+
import dagger.Module;
22+
import dagger.Provides;
23+
import javax.inject.Singleton;
24+
25+
@Module
26+
public abstract class SpyEventStoreModule {
27+
@Provides
28+
static EventStoreConfig storeConfig() {
29+
return EventStoreConfig.DEFAULT;
30+
}
31+
32+
@Provides
33+
@Singleton
34+
static EventStore eventStore(SQLiteEventStore store) {
35+
return spy(store);
36+
}
37+
38+
@Binds
39+
abstract SynchronizationGuard synchronizationGuard(SQLiteEventStore store);
40+
}

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/AlarmManagerScheduler.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,7 @@ public void schedule(TransportContext transportContext, int attemptNumber) {
9494

9595
Long backendTime = eventStore.getNextCallTime(transportContext);
9696

97-
long timeDiff = 0;
98-
if (backendTime != null) {
99-
timeDiff = backendTime - clock.getTime();
100-
}
97+
long timeDiff = Math.max(0, backendTime - clock.getTime());
10198

10299
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
103100
this.alarmManager.set(

0 commit comments

Comments
 (0)