1 /*
2  * Copyright (C) 2016, The Android Open Source Project
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.android.server.connectivity;
18 
19 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
20 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.fail;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.when;
27 
28 import android.content.Context;
29 import android.net.ConnectivityManager;
30 import android.net.ConnectivityMetricsEvent;
31 import android.net.IIpConnectivityMetrics;
32 import android.net.IpPrefix;
33 import android.net.LinkAddress;
34 import android.net.LinkProperties;
35 import android.net.Network;
36 import android.net.NetworkCapabilities;
37 import android.net.RouteInfo;
38 import android.net.metrics.ApfProgramEvent;
39 import android.net.metrics.ApfStats;
40 import android.net.metrics.DhcpClientEvent;
41 import android.net.metrics.IpConnectivityLog;
42 import android.net.metrics.IpManagerEvent;
43 import android.net.metrics.IpReachabilityEvent;
44 import android.net.metrics.RaEvent;
45 import android.net.metrics.ValidationProbeEvent;
46 import android.os.Parcelable;
47 import android.system.OsConstants;
48 import android.test.suitebuilder.annotation.SmallTest;
49 import android.util.Base64;
50 
51 import androidx.test.runner.AndroidJUnit4;
52 
53 import com.android.internal.util.BitUtils;
54 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
55 
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.mockito.Mock;
60 import org.mockito.MockitoAnnotations;
61 
62 import java.io.PrintWriter;
63 import java.io.StringWriter;
64 
65 @RunWith(AndroidJUnit4.class)
66 @SmallTest
67 public class IpConnectivityMetricsTest {
68     static final IpReachabilityEvent FAKE_EV =
69             new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
70 
71     private static final String EXAMPLE_IPV4 = "192.0.2.1";
72     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
73 
74     private static final byte[] MAC_ADDR =
75             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
76 
77     @Mock Context mCtx;
78     @Mock IIpConnectivityMetrics mMockService;
79     @Mock ConnectivityManager mCm;
80 
81     IpConnectivityMetrics mService;
82     NetdEventListenerService mNetdListener;
83 
84     @Before
setUp()85     public void setUp() {
86         MockitoAnnotations.initMocks(this);
87         mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
88         mNetdListener = new NetdEventListenerService(mCm);
89         mService.mNetdListener = mNetdListener;
90     }
91 
92     @Test
testBufferFlushing()93     public void testBufferFlushing() {
94         String output1 = getdump("flush");
95         assertEquals("", output1);
96 
97         new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
98         String output2 = getdump("flush");
99         assertFalse("".equals(output2));
100 
101         String output3 = getdump("flush");
102         assertEquals("", output3);
103     }
104 
105     @Test
testRateLimiting()106     public void testRateLimiting() {
107         final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
108         final ApfProgramEvent ev = new ApfProgramEvent.Builder().build();
109         final long fakeTimestamp = 1;
110 
111         int attempt = 100; // More than burst quota, but less than buffer size.
112         for (int i = 0; i < attempt; i++) {
113             logger.log(ev);
114         }
115 
116         String output1 = getdump("flush");
117         assertFalse("".equals(output1));
118 
119         for (int i = 0; i < attempt; i++) {
120             assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
121         }
122 
123         String output2 = getdump("flush");
124         assertEquals("", output2);
125     }
126 
127     @Test
testDefaultNetworkEvents()128     public void testDefaultNetworkEvents() throws Exception {
129         final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
130         final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
131 
132         NetworkAgentInfo[][] defaultNetworks = {
133             // nothing -> cell
134             {null, makeNai(100, 10, false, true, cell)},
135             // cell -> wifi
136             {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)},
137             // wifi -> nothing
138             {makeNai(101, 60, true, false, wifi), null},
139             // nothing -> cell
140             {null, makeNai(102, 10, true, true, cell)},
141             // cell -> wifi
142             {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
143         };
144 
145         long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
146         long durationMs = 1001;
147         for (NetworkAgentInfo[] pair : defaultNetworks) {
148             timeMs += durationMs;
149             durationMs += durationMs;
150             mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, pair[1], pair[0]);
151         }
152 
153         String want = String.join("\n",
154                 "dropped_events: 0",
155                 "events <",
156                 "  if_name: \"\"",
157                 "  link_layer: 5",
158                 "  network_id: 0",
159                 "  time_ms: 0",
160                 "  transports: 0",
161                 "  default_network_event <",
162                 "    default_network_duration_ms: 1001",
163                 "    final_score: 0",
164                 "    initial_score: 0",
165                 "    ip_support: 0",
166                 "    no_default_network_duration_ms: 0",
167                 "    previous_default_network_link_layer: 0",
168                 "    previous_network_ip_support: 0",
169                 "    validation_duration_ms: 0",
170                 "  >",
171                 ">",
172                 "events <",
173                 "  if_name: \"\"",
174                 "  link_layer: 2",
175                 "  network_id: 100",
176                 "  time_ms: 0",
177                 "  transports: 1",
178                 "  default_network_event <",
179                 "    default_network_duration_ms: 2002",
180                 "    final_score: 50",
181                 "    initial_score: 10",
182                 "    ip_support: 3",
183                 "    no_default_network_duration_ms: 0",
184                 "    previous_default_network_link_layer: 0",
185                 "    previous_network_ip_support: 0",
186                 "    validation_duration_ms: 2002",
187                 "  >",
188                 ">",
189                 "events <",
190                 "  if_name: \"\"",
191                 "  link_layer: 4",
192                 "  network_id: 101",
193                 "  time_ms: 0",
194                 "  transports: 2",
195                 "  default_network_event <",
196                 "    default_network_duration_ms: 4004",
197                 "    final_score: 60",
198                 "    initial_score: 20",
199                 "    ip_support: 1",
200                 "    no_default_network_duration_ms: 0",
201                 "    previous_default_network_link_layer: 2",
202                 "    previous_network_ip_support: 0",
203                 "    validation_duration_ms: 4004",
204                 "  >",
205                 ">",
206                 "events <",
207                 "  if_name: \"\"",
208                 "  link_layer: 5",
209                 "  network_id: 0",
210                 "  time_ms: 0",
211                 "  transports: 0",
212                 "  default_network_event <",
213                 "    default_network_duration_ms: 8008",
214                 "    final_score: 0",
215                 "    initial_score: 0",
216                 "    ip_support: 0",
217                 "    no_default_network_duration_ms: 0",
218                 "    previous_default_network_link_layer: 4",
219                 "    previous_network_ip_support: 0",
220                 "    validation_duration_ms: 0",
221                 "  >",
222                 ">",
223                 "events <",
224                 "  if_name: \"\"",
225                 "  link_layer: 2",
226                 "  network_id: 102",
227                 "  time_ms: 0",
228                 "  transports: 1",
229                 "  default_network_event <",
230                 "    default_network_duration_ms: 16016",
231                 "    final_score: 50",
232                 "    initial_score: 10",
233                 "    ip_support: 3",
234                 "    no_default_network_duration_ms: 0",
235                 "    previous_default_network_link_layer: 4",
236                 "    previous_network_ip_support: 0",
237                 "    validation_duration_ms: 16016",
238                 "  >",
239                 ">",
240                 "version: 2\n");
241 
242         verifySerialization(want, getdump("flush"));
243     }
244 
245     @Test
testEndToEndLogging()246     public void testEndToEndLogging() throws Exception {
247         // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
248         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
249 
250         NetworkCapabilities ncWifi = new NetworkCapabilities();
251         NetworkCapabilities ncCell = new NetworkCapabilities();
252         ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
253         ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
254 
255         when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
256         when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
257 
258         ApfStats apfStats = new ApfStats.Builder()
259                 .setDurationMs(45000)
260                 .setReceivedRas(10)
261                 .setMatchingRas(2)
262                 .setDroppedRas(2)
263                 .setParseErrors(2)
264                 .setZeroLifetimeRas(1)
265                 .setProgramUpdates(4)
266                 .setProgramUpdatesAll(7)
267                 .setProgramUpdatesAllowingMulticast(3)
268                 .setMaxProgramSize(2048)
269                 .build();
270 
271         final ValidationProbeEvent validationEv = new ValidationProbeEvent.Builder()
272                 .setDurationMs(40730)
273                 .setProbeType(ValidationProbeEvent.PROBE_HTTP, true)
274                 .setReturnCode(204)
275                 .build();
276 
277         final DhcpClientEvent event = new DhcpClientEvent.Builder()
278                 .setMsg("SomeState")
279                 .setDurationMs(192)
280                 .build();
281         Parcelable[] events = {
282             new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), event,
283             new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
284             validationEv,
285             apfStats,
286             new RaEvent(2000, 400, 300, -1, 1000, -1)
287         };
288 
289         for (int i = 0; i < events.length; i++) {
290             ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
291             ev.timestamp = 100 * (i + 1);
292             ev.ifname = "wlan0";
293             ev.data = events[i];
294             logger.log(ev);
295         }
296 
297         // netId, errno, latency, destination
298         connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4);
299         connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6);
300         connectEvent(100, 0, 110, EXAMPLE_IPV4);
301         connectEvent(101, 0, 23, EXAMPLE_IPV4);
302         connectEvent(101, 0, 45, EXAMPLE_IPV6);
303         connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4);
304 
305         // netId, type, return code, latency
306         dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
307         dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
308         dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638);
309         dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
310         dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
311 
312         // iface, uid
313         final byte[] mac = {0x48, 0x7c, 0x2b, 0x6a, 0x3e, 0x4b};
314         final String srcIp = "192.168.2.1";
315         final String dstIp = "192.168.2.23";
316         final int sport = 2356;
317         final int dport = 13489;
318         final long now = 1001L;
319         final int v4 = 0x800;
320         final int tcp = 6;
321         final int udp = 17;
322         wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
323         wakeupEvent("wlan0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
324         wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
325         wakeupEvent("wlan0", 10008, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
326         wakeupEvent("wlan0", -1, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
327         wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
328 
329         long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
330         final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
331         final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
332         NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
333         NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
334         mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 200, cellNai, null);
335         mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 300, wifiNai, cellNai);
336 
337         String want = String.join("\n",
338                 "dropped_events: 0",
339                 "events <",
340                 "  if_name: \"\"",
341                 "  link_layer: 4",
342                 "  network_id: 0",
343                 "  time_ms: 100",
344                 "  transports: 0",
345                 "  ip_reachability_event <",
346                 "    event_type: 512",
347                 "    if_name: \"\"",
348                 "  >",
349                 ">",
350                 "events <",
351                 "  if_name: \"\"",
352                 "  link_layer: 4",
353                 "  network_id: 0",
354                 "  time_ms: 200",
355                 "  transports: 0",
356                 "  dhcp_event <",
357                 "    duration_ms: 192",
358                 "    if_name: \"\"",
359                 "    state_transition: \"SomeState\"",
360                 "  >",
361                 ">",
362                 "events <",
363                 "  if_name: \"\"",
364                 "  link_layer: 4",
365                 "  network_id: 0",
366                 "  time_ms: 300",
367                 "  transports: 0",
368                 "  ip_provisioning_event <",
369                 "    event_type: 1",
370                 "    if_name: \"\"",
371                 "    latency_ms: 5678",
372                 "  >",
373                 ">",
374                 "events <",
375                 "  if_name: \"\"",
376                 "  link_layer: 4",
377                 "  network_id: 0",
378                 "  time_ms: 400",
379                 "  transports: 0",
380                 "  validation_probe_event <",
381                 "    latency_ms: 40730",
382                 "    probe_result: 204",
383                 "    probe_type: 257",
384                 "  >",
385                 ">",
386                 "events <",
387                 "  if_name: \"\"",
388                 "  link_layer: 4",
389                 "  network_id: 0",
390                 "  time_ms: 500",
391                 "  transports: 0",
392                 "  apf_statistics <",
393                 "    dropped_ras: 2",
394                 "    duration_ms: 45000",
395                 "    matching_ras: 2",
396                 "    max_program_size: 2048",
397                 "    parse_errors: 2",
398                 "    program_updates: 4",
399                 "    program_updates_all: 7",
400                 "    program_updates_allowing_multicast: 3",
401                 "    received_ras: 10",
402                 "    total_packet_dropped: 0",
403                 "    total_packet_processed: 0",
404                 "    zero_lifetime_ras: 1",
405                 "  >",
406                 ">",
407                 "events <",
408                 "  if_name: \"\"",
409                 "  link_layer: 4",
410                 "  network_id: 0",
411                 "  time_ms: 600",
412                 "  transports: 0",
413                 "  ra_event <",
414                 "    dnssl_lifetime: -1",
415                 "    prefix_preferred_lifetime: 300",
416                 "    prefix_valid_lifetime: 400",
417                 "    rdnss_lifetime: 1000",
418                 "    route_info_lifetime: -1",
419                 "    router_lifetime: 2000",
420                 "  >",
421                 ">",
422                 "events <",
423                 "  if_name: \"\"",
424                 "  link_layer: 5",
425                 "  network_id: 0",
426                 "  time_ms: 0",
427                 "  transports: 0",
428                 "  default_network_event <",
429                 "    default_network_duration_ms: 200",
430                 "    final_score: 0",
431                 "    initial_score: 0",
432                 "    ip_support: 0",
433                 "    no_default_network_duration_ms: 0",
434                 "    previous_default_network_link_layer: 0",
435                 "    previous_network_ip_support: 0",
436                 "    validation_duration_ms: 0",
437                 "  >",
438                 ">",
439                 "events <",
440                 "  if_name: \"\"",
441                 "  link_layer: 2",
442                 "  network_id: 100",
443                 "  time_ms: 0",
444                 "  transports: 1",
445                 "  default_network_event <",
446                 "    default_network_duration_ms: 100",
447                 "    final_score: 50",
448                 "    initial_score: 50",
449                 "    ip_support: 2",
450                 "    no_default_network_duration_ms: 0",
451                 "    previous_default_network_link_layer: 0",
452                 "    previous_network_ip_support: 0",
453                 "    validation_duration_ms: 100",
454                 "  >",
455                 ">",
456                 "events <",
457                 "  if_name: \"\"",
458                 "  link_layer: 4",
459                 "  network_id: 100",
460                 "  time_ms: 0",
461                 "  transports: 2",
462                 "  connect_statistics <",
463                 "    connect_blocking_count: 1",
464                 "    connect_count: 3",
465                 "    errnos_counters <",
466                 "      key: 11",
467                 "      value: 1",
468                 "    >",
469                 "    ipv6_addr_count: 1",
470                 "    latencies_ms: 110",
471                 "  >",
472                 ">",
473                 "events <",
474                 "  if_name: \"\"",
475                 "  link_layer: 2",
476                 "  network_id: 101",
477                 "  time_ms: 0",
478                 "  transports: 1",
479                 "  connect_statistics <",
480                 "    connect_blocking_count: 2",
481                 "    connect_count: 2",
482                 "    ipv6_addr_count: 1",
483                 "    latencies_ms: 23",
484                 "    latencies_ms: 45",
485                 "  >",
486                 ">",
487                 "events <",
488                 "  if_name: \"\"",
489                 "  link_layer: 4",
490                 "  network_id: 100",
491                 "  time_ms: 0",
492                 "  transports: 2",
493                 "  dns_lookup_batch <",
494                 "    event_types: 1",
495                 "    event_types: 1",
496                 "    event_types: 2",
497                 "    getaddrinfo_error_count: 0",
498                 "    getaddrinfo_query_count: 0",
499                 "    gethostbyname_error_count: 0",
500                 "    gethostbyname_query_count: 0",
501                 "    latencies_ms: 3456",
502                 "    latencies_ms: 45",
503                 "    latencies_ms: 638",
504                 "    return_codes: 0",
505                 "    return_codes: 3",
506                 "    return_codes: 0",
507                 "  >",
508                 ">",
509                 "events <",
510                 "  if_name: \"\"",
511                 "  link_layer: 2",
512                 "  network_id: 101",
513                 "  time_ms: 0",
514                 "  transports: 1",
515                 "  dns_lookup_batch <",
516                 "    event_types: 1",
517                 "    event_types: 2",
518                 "    getaddrinfo_error_count: 0",
519                 "    getaddrinfo_query_count: 0",
520                 "    gethostbyname_error_count: 0",
521                 "    gethostbyname_query_count: 0",
522                 "    latencies_ms: 56",
523                 "    latencies_ms: 34",
524                 "    return_codes: 0",
525                 "    return_codes: 0",
526                 "  >",
527                 ">",
528                 "events <",
529                 "  if_name: \"\"",
530                 "  link_layer: 4",
531                 "  network_id: 0",
532                 "  time_ms: 0",
533                 "  transports: 0",
534                 "  wakeup_stats <",
535                 "    application_wakeups: 3",
536                 "    duration_sec: 0",
537                 "    ethertype_counts <",
538                 "      key: 2048",
539                 "      value: 6",
540                 "    >",
541                 "    ip_next_header_counts <",
542                 "      key: 6",
543                 "      value: 3",
544                 "    >",
545                 "    ip_next_header_counts <",
546                 "      key: 17",
547                 "      value: 3",
548                 "    >",
549                 "    l2_broadcast_count: 0",
550                 "    l2_multicast_count: 0",
551                 "    l2_unicast_count: 6",
552                 "    no_uid_wakeups: 1",
553                 "    non_application_wakeups: 0",
554                 "    root_wakeups: 0",
555                 "    system_wakeups: 2",
556                 "    total_wakeups: 6",
557                 "  >",
558                 ">",
559                 "version: 2\n");
560 
561         verifySerialization(want, getdump("flush"));
562     }
563 
getdump(String .... command)564     String getdump(String ... command) {
565         StringWriter buffer = new StringWriter();
566         PrintWriter writer = new PrintWriter(buffer);
567         mService.impl.dump(null, writer, command);
568         return buffer.toString();
569     }
570 
connectEvent(int netid, int error, int latencyMs, String ipAddr)571     void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
572         mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
573     }
574 
dnsEvent(int netId, int type, int result, int latency)575     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
576         mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
577     }
578 
wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp, String dstIp, int sport, int dport, long now)579     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
580             String dstIp, int sport, int dport, long now) throws Exception {
581         String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
582         mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
583     }
584 
makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports)585     NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) {
586         NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
587         when(nai.network()).thenReturn(new Network(netId));
588         when(nai.getCurrentScore()).thenReturn(score);
589         nai.linkProperties = new LinkProperties();
590         nai.networkCapabilities = new NetworkCapabilities();
591         nai.lastValidated = true;
592         for (int t : BitUtils.unpackBits(transports)) {
593             nai.networkCapabilities.addTransportType(t);
594         }
595         if (ipv4) {
596             nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24"));
597             nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
598         }
599         if (ipv6) {
600             nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64"));
601             nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0")));
602         }
603         return nai;
604     }
605 
606 
607 
verifySerialization(String want, String output)608     static void verifySerialization(String want, String output) {
609         try {
610             byte[] got = Base64.decode(output, Base64.DEFAULT);
611             IpConnectivityLogClass.IpConnectivityLog log =
612                     IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
613             assertEquals(want, log.toString());
614         } catch (Exception e) {
615             fail(e.toString());
616         }
617     }
618 }
619