1 /*
2  * Copyright (C) 2019 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 android.util;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static java.nio.charset.StandardCharsets.UTF_8;
23 
24 import android.os.SystemClock;
25 
26 import androidx.test.filters.SmallTest;
27 import androidx.test.runner.AndroidJUnit4;
28 
29 import com.google.common.collect.Range;
30 
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 
34 import java.nio.ByteBuffer;
35 import java.nio.ByteOrder;
36 import java.util.Random;
37 
38 /**
39  * Internal tests for {@link StatsEvent}.
40  */
41 @SmallTest
42 @RunWith(AndroidJUnit4.class)
43 public class StatsEventTest {
44 
45     @Test
testNoFields()46     public void testNoFields() {
47         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
48         final StatsEvent statsEvent = StatsEvent.newBuilder().usePooledBuffer().build();
49         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
50 
51         final int expectedAtomId = 0;
52         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
53 
54         final ByteBuffer buffer =
55                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
56 
57         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
58                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
59 
60         assertWithMessage("Incorrect number of elements in root object")
61                 .that(buffer.get()).isEqualTo(3);
62 
63         assertWithMessage("First element is not timestamp")
64                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
65 
66         assertWithMessage("Incorrect timestamp")
67                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
68 
69         assertWithMessage("Second element is not atom id")
70                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
71 
72         assertWithMessage("Incorrect atom id")
73                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
74 
75         assertWithMessage("Third element is not errors type")
76                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS);
77 
78         final int errorMask = buffer.getInt();
79 
80         assertWithMessage("ERROR_NO_ATOM_ID should be the only error in the error mask")
81                 .that(errorMask).isEqualTo(StatsEvent.ERROR_NO_ATOM_ID);
82 
83         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
84 
85         statsEvent.release();
86     }
87 
88     @Test
testOnlyAtomId()89     public void testOnlyAtomId() {
90         final int expectedAtomId = 109;
91 
92         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
93         final StatsEvent statsEvent = StatsEvent.newBuilder()
94                 .setAtomId(expectedAtomId)
95                 .usePooledBuffer()
96                 .build();
97         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
98 
99         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
100 
101         final ByteBuffer buffer =
102                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
103 
104         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
105                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
106 
107         assertWithMessage("Incorrect number of elements in root object")
108                 .that(buffer.get()).isEqualTo(2);
109 
110         assertWithMessage("First element is not timestamp")
111                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
112 
113         assertWithMessage("Incorrect timestamp")
114                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
115 
116         assertWithMessage("Second element is not atom id")
117                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
118 
119         assertWithMessage("Incorrect atom id")
120                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
121 
122         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
123 
124         statsEvent.release();
125     }
126 
127     @Test
testIntBooleanIntInt()128     public void testIntBooleanIntInt() {
129         final int expectedAtomId = 109;
130         final int field1 = 1;
131         final boolean field2 = true;
132         final int field3 = 3;
133         final int field4 = 4;
134 
135         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
136         final StatsEvent statsEvent = StatsEvent.newBuilder()
137                 .setAtomId(expectedAtomId)
138                 .writeInt(field1)
139                 .writeBoolean(field2)
140                 .writeInt(field3)
141                 .writeInt(field4)
142                 .usePooledBuffer()
143                 .build();
144         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
145 
146         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
147 
148         final ByteBuffer buffer =
149                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
150 
151         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
152                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
153 
154         assertWithMessage("Incorrect number of elements in root object")
155                 .that(buffer.get()).isEqualTo(6);
156 
157         assertWithMessage("First element is not timestamp")
158                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
159 
160         assertWithMessage("Incorrect timestamp")
161                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
162 
163         assertWithMessage("Second element is not atom id")
164                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
165 
166         assertWithMessage("Incorrect atom id")
167                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
168 
169         assertWithMessage("First field is not Int")
170                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
171 
172         assertWithMessage("Incorrect field 1")
173                 .that(buffer.getInt()).isEqualTo(field1);
174 
175         assertWithMessage("Second field is not Boolean")
176                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
177 
178         assertWithMessage("Incorrect field 2")
179                 .that(buffer.get()).isEqualTo(1);
180 
181         assertWithMessage("Third field is not Int")
182                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
183 
184         assertWithMessage("Incorrect field 3")
185                 .that(buffer.getInt()).isEqualTo(field3);
186 
187         assertWithMessage("Fourth field is not Int")
188                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
189 
190         assertWithMessage("Incorrect field 4")
191                 .that(buffer.getInt()).isEqualTo(field4);
192 
193         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
194 
195         statsEvent.release();
196     }
197 
198     @Test
testStringFloatByteArray()199     public void testStringFloatByteArray() {
200         final int expectedAtomId = 109;
201         final String field1 = "Str 1";
202         final float field2 = 9.334f;
203         final byte[] field3 = new byte[] { 56, 23, 89, -120 };
204 
205         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
206         final StatsEvent statsEvent = StatsEvent.newBuilder()
207                 .setAtomId(expectedAtomId)
208                 .writeString(field1)
209                 .writeFloat(field2)
210                 .writeByteArray(field3)
211                 .usePooledBuffer()
212                 .build();
213         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
214 
215         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
216 
217         final ByteBuffer buffer =
218                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
219 
220         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
221                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
222 
223         assertWithMessage("Incorrect number of elements in root object")
224                 .that(buffer.get()).isEqualTo(5);
225 
226         assertWithMessage("First element is not timestamp")
227                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
228 
229         assertWithMessage("Incorrect timestamp")
230                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
231 
232         assertWithMessage("Second element is not atom id")
233                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
234 
235         assertWithMessage("Incorrect atom id")
236                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
237 
238         assertWithMessage("First field is not String")
239                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING);
240 
241         final String field1Actual = getStringFromByteBuffer(buffer);
242         assertWithMessage("Incorrect field 1")
243                 .that(field1Actual).isEqualTo(field1);
244 
245         assertWithMessage("Second field is not Float")
246                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT);
247 
248         assertWithMessage("Incorrect field 2")
249                 .that(buffer.getFloat()).isEqualTo(field2);
250 
251         assertWithMessage("Third field is not byte array")
252                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
253 
254         final byte[] field3Actual = getByteArrayFromByteBuffer(buffer);
255         assertWithMessage("Incorrect field 3")
256                 .that(field3Actual).isEqualTo(field3);
257 
258         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
259 
260         statsEvent.release();
261     }
262 
263     @Test
testAttributionChainLong()264     public void testAttributionChainLong() {
265         final int expectedAtomId = 109;
266         final int[] uids = new int[] { 1, 2, 3, 4, 5 };
267         final String[] tags = new String[] { "1", "2", "3", "4", "5" };
268         final long field2 = -230909823L;
269 
270         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
271         final StatsEvent statsEvent = StatsEvent.newBuilder()
272                 .setAtomId(expectedAtomId)
273                 .writeAttributionChain(uids, tags)
274                 .writeLong(field2)
275                 .usePooledBuffer()
276                 .build();
277         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
278 
279         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
280 
281         final ByteBuffer buffer =
282                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
283 
284         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
285                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
286 
287         assertWithMessage("Incorrect number of elements in root object")
288                 .that(buffer.get()).isEqualTo(4);
289 
290         assertWithMessage("First element is not timestamp")
291                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
292 
293         assertWithMessage("Incorrect timestamp")
294                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
295 
296         assertWithMessage("Second element is not atom id")
297                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
298 
299         assertWithMessage("Incorrect atom id")
300                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
301 
302         assertWithMessage("First field is not Attribution Chain")
303                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ATTRIBUTION_CHAIN);
304 
305         assertWithMessage("Incorrect number of attribution nodes")
306                 .that(buffer.get()).isEqualTo((byte) uids.length);
307 
308         for (int i = 0; i < tags.length; i++) {
309             assertWithMessage("Incorrect uid in Attribution Chain")
310                     .that(buffer.getInt()).isEqualTo(uids[i]);
311 
312             final String tag = getStringFromByteBuffer(buffer);
313             assertWithMessage("Incorrect tag in Attribution Chain")
314                     .that(tag).isEqualTo(tags[i]);
315         }
316 
317         assertWithMessage("Second field is not Long")
318                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
319 
320         assertWithMessage("Incorrect field 2")
321                 .that(buffer.getLong()).isEqualTo(field2);
322 
323         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
324 
325         statsEvent.release();
326     }
327 
328     @Test
testKeyValuePairs()329     public void testKeyValuePairs() {
330         final int expectedAtomId = 109;
331         final SparseIntArray intMap = new SparseIntArray();
332         final SparseLongArray longMap = new SparseLongArray();
333         final SparseArray<String> stringMap = new SparseArray<>();
334         final SparseArray<Float> floatMap = new SparseArray<>();
335         intMap.put(1, -1);
336         intMap.put(2, -2);
337         stringMap.put(3, "abc");
338         stringMap.put(4, "2h");
339         floatMap.put(9, -234.344f);
340 
341         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
342         final StatsEvent statsEvent = StatsEvent.newBuilder()
343                 .setAtomId(expectedAtomId)
344                 .writeKeyValuePairs(intMap, longMap, stringMap, floatMap)
345                 .usePooledBuffer()
346                 .build();
347         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
348 
349         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
350 
351         final ByteBuffer buffer =
352                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
353 
354         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
355                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
356 
357         assertWithMessage("Incorrect number of elements in root object")
358                 .that(buffer.get()).isEqualTo(3);
359 
360         assertWithMessage("First element is not timestamp")
361                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
362 
363         assertWithMessage("Incorrect timestamp")
364                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
365 
366         assertWithMessage("Second element is not atom id")
367                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
368 
369         assertWithMessage("Incorrect atom id")
370                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
371 
372         assertWithMessage("First field is not KeyValuePairs")
373                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_KEY_VALUE_PAIRS);
374 
375         assertWithMessage("Incorrect number of key value pairs")
376                 .that(buffer.get()).isEqualTo(
377                         (byte) (intMap.size() + longMap.size() + stringMap.size()
378                                 + floatMap.size()));
379 
380         for (int i = 0; i < intMap.size(); i++) {
381             assertWithMessage("Incorrect key in intMap")
382                     .that(buffer.getInt()).isEqualTo(intMap.keyAt(i));
383             assertWithMessage("The type id of the value should be TYPE_INT in intMap")
384                     .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
385             assertWithMessage("Incorrect value in intMap")
386                     .that(buffer.getInt()).isEqualTo(intMap.valueAt(i));
387         }
388 
389         for (int i = 0; i < longMap.size(); i++) {
390             assertWithMessage("Incorrect key in longMap")
391                     .that(buffer.getInt()).isEqualTo(longMap.keyAt(i));
392             assertWithMessage("The type id of the value should be TYPE_LONG in longMap")
393                     .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
394             assertWithMessage("Incorrect value in longMap")
395                     .that(buffer.getLong()).isEqualTo(longMap.valueAt(i));
396         }
397 
398         for (int i = 0; i < stringMap.size(); i++) {
399             assertWithMessage("Incorrect key in stringMap")
400                     .that(buffer.getInt()).isEqualTo(stringMap.keyAt(i));
401             assertWithMessage("The type id of the value should be TYPE_STRING in stringMap")
402                     .that(buffer.get()).isEqualTo(StatsEvent.TYPE_STRING);
403             final String value = getStringFromByteBuffer(buffer);
404             assertWithMessage("Incorrect value in stringMap")
405                     .that(value).isEqualTo(stringMap.valueAt(i));
406         }
407 
408         for (int i = 0; i < floatMap.size(); i++) {
409             assertWithMessage("Incorrect key in floatMap")
410                     .that(buffer.getInt()).isEqualTo(floatMap.keyAt(i));
411             assertWithMessage("The type id of the value should be TYPE_FLOAT in floatMap")
412                     .that(buffer.get()).isEqualTo(StatsEvent.TYPE_FLOAT);
413             assertWithMessage("Incorrect value in floatMap")
414                     .that(buffer.getFloat()).isEqualTo(floatMap.valueAt(i));
415         }
416 
417         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
418 
419         statsEvent.release();
420     }
421 
422     @Test
testSingleAnnotations()423     public void testSingleAnnotations() {
424         final int expectedAtomId = 109;
425         final int field1 = 1;
426         final byte field1AnnotationId = 45;
427         final boolean field1AnnotationValue = false;
428         final boolean field2 = true;
429         final byte field2AnnotationId = 1;
430         final int field2AnnotationValue = 23;
431 
432         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
433         final StatsEvent statsEvent = StatsEvent.newBuilder()
434                 .setAtomId(expectedAtomId)
435                 .writeInt(field1)
436                 .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
437                 .writeBoolean(field2)
438                 .addIntAnnotation(field2AnnotationId, field2AnnotationValue)
439                 .usePooledBuffer()
440                 .build();
441         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
442 
443         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
444 
445         final ByteBuffer buffer =
446                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
447 
448         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
449                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
450 
451         assertWithMessage("Incorrect number of elements in root object")
452                 .that(buffer.get()).isEqualTo(4);
453 
454         assertWithMessage("First element is not timestamp")
455                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
456 
457         assertWithMessage("Incorrect timestamp")
458                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
459 
460         assertWithMessage("Second element is not atom id")
461                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
462 
463         assertWithMessage("Incorrect atom id")
464                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
465 
466         final byte field1Header = buffer.get();
467         final int field1AnnotationValueCount = field1Header >> 4;
468         final byte field1Type = (byte) (field1Header & 0x0F);
469         assertWithMessage("First field is not Int")
470                 .that(field1Type).isEqualTo(StatsEvent.TYPE_INT);
471         assertWithMessage("First field annotation count is wrong")
472                 .that(field1AnnotationValueCount).isEqualTo(1);
473         assertWithMessage("Incorrect field 1")
474                 .that(buffer.getInt()).isEqualTo(field1);
475         assertWithMessage("First field's annotation id is wrong")
476                 .that(buffer.get()).isEqualTo(field1AnnotationId);
477         assertWithMessage("First field's annotation type is wrong")
478                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
479         assertWithMessage("First field's annotation value is wrong")
480                 .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0);
481 
482         final byte field2Header = buffer.get();
483         final int field2AnnotationValueCount = field2Header >> 4;
484         final byte field2Type = (byte) (field2Header & 0x0F);
485         assertWithMessage("Second field is not boolean")
486                 .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN);
487         assertWithMessage("Second field annotation count is wrong")
488                 .that(field2AnnotationValueCount).isEqualTo(1);
489         assertWithMessage("Incorrect field 2")
490                 .that(buffer.get()).isEqualTo(field2 ? 1 : 0);
491         assertWithMessage("Second field's annotation id is wrong")
492                 .that(buffer.get()).isEqualTo(field2AnnotationId);
493         assertWithMessage("Second field's annotation type is wrong")
494                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
495         assertWithMessage("Second field's annotation value is wrong")
496                 .that(buffer.getInt()).isEqualTo(field2AnnotationValue);
497 
498         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
499 
500         statsEvent.release();
501     }
502 
503     @Test
testAtomIdAnnotations()504     public void testAtomIdAnnotations() {
505         final int expectedAtomId = 109;
506         final byte atomAnnotationId = 84;
507         final int atomAnnotationValue = 9;
508         final int field1 = 1;
509         final byte field1AnnotationId = 45;
510         final boolean field1AnnotationValue = false;
511         final boolean field2 = true;
512         final byte field2AnnotationId = 1;
513         final int field2AnnotationValue = 23;
514 
515         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
516         final StatsEvent statsEvent = StatsEvent.newBuilder()
517                 .setAtomId(expectedAtomId)
518                 .addIntAnnotation(atomAnnotationId, atomAnnotationValue)
519                 .writeInt(field1)
520                 .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
521                 .writeBoolean(field2)
522                 .addIntAnnotation(field2AnnotationId, field2AnnotationValue)
523                 .usePooledBuffer()
524                 .build();
525         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
526 
527         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
528 
529         final ByteBuffer buffer =
530                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
531 
532         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
533                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
534 
535         assertWithMessage("Incorrect number of elements in root object")
536                 .that(buffer.get()).isEqualTo(4);
537 
538         assertWithMessage("First element is not timestamp")
539                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
540 
541         assertWithMessage("Incorrect timestamp")
542                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
543 
544         final byte atomIdHeader = buffer.get();
545         final int atomIdAnnotationValueCount = atomIdHeader >> 4;
546         final byte atomIdValueType = (byte) (atomIdHeader & 0x0F);
547         assertWithMessage("Second element is not atom id")
548                 .that(atomIdValueType).isEqualTo(StatsEvent.TYPE_INT);
549         assertWithMessage("Atom id annotation count is wrong")
550                 .that(atomIdAnnotationValueCount).isEqualTo(1);
551         assertWithMessage("Incorrect atom id")
552                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
553         assertWithMessage("Atom id's annotation id is wrong")
554                 .that(buffer.get()).isEqualTo(atomAnnotationId);
555         assertWithMessage("Atom id's annotation type is wrong")
556                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
557         assertWithMessage("Atom id's annotation value is wrong")
558                 .that(buffer.getInt()).isEqualTo(atomAnnotationValue);
559 
560         final byte field1Header = buffer.get();
561         final int field1AnnotationValueCount = field1Header >> 4;
562         final byte field1Type = (byte) (field1Header & 0x0F);
563         assertWithMessage("First field is not Int")
564                 .that(field1Type).isEqualTo(StatsEvent.TYPE_INT);
565         assertWithMessage("First field annotation count is wrong")
566                 .that(field1AnnotationValueCount).isEqualTo(1);
567         assertWithMessage("Incorrect field 1")
568                 .that(buffer.getInt()).isEqualTo(field1);
569         assertWithMessage("First field's annotation id is wrong")
570                 .that(buffer.get()).isEqualTo(field1AnnotationId);
571         assertWithMessage("First field's annotation type is wrong")
572                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN);
573         assertWithMessage("First field's annotation value is wrong")
574                 .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0);
575 
576         final byte field2Header = buffer.get();
577         final int field2AnnotationValueCount = field2Header >> 4;
578         final byte field2Type = (byte) (field2Header & 0x0F);
579         assertWithMessage("Second field is not boolean")
580                 .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN);
581         assertWithMessage("Second field annotation count is wrong")
582                 .that(field2AnnotationValueCount).isEqualTo(1);
583         assertWithMessage("Incorrect field 2")
584                 .that(buffer.get()).isEqualTo(field2 ? 1 : 0);
585         assertWithMessage("Second field's annotation id is wrong")
586                 .that(buffer.get()).isEqualTo(field2AnnotationId);
587         assertWithMessage("Second field's annotation type is wrong")
588                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
589         assertWithMessage("Second field's annotation value is wrong")
590                 .that(buffer.getInt()).isEqualTo(field2AnnotationValue);
591 
592         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
593 
594         statsEvent.release();
595     }
596 
597     @Test
testSetAtomIdNotCalledImmediately()598     public void testSetAtomIdNotCalledImmediately() {
599         final int expectedAtomId = 109;
600         final int field1 = 25;
601         final boolean field2 = true;
602 
603         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
604         final StatsEvent statsEvent = StatsEvent.newBuilder()
605                 .writeInt(field1)
606                 .setAtomId(expectedAtomId)
607                 .writeBoolean(field2)
608                 .usePooledBuffer()
609                 .build();
610         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
611 
612         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
613 
614         final ByteBuffer buffer =
615                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
616 
617         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
618                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT);
619 
620         assertWithMessage("Incorrect number of elements in root object")
621                 .that(buffer.get()).isEqualTo(3);
622 
623         assertWithMessage("First element is not timestamp")
624                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG);
625 
626         assertWithMessage("Incorrect timestamp")
627                 .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp));
628 
629         assertWithMessage("Second element is not atom id")
630                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT);
631 
632         assertWithMessage("Incorrect atom id")
633                 .that(buffer.getInt()).isEqualTo(expectedAtomId);
634 
635         assertWithMessage("Third element is not errors type")
636                 .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS);
637 
638         final int errorMask = buffer.getInt();
639 
640         assertWithMessage("ERROR_ATOM_ID_INVALID_POSITION should be the only error in the mask")
641                 .that(errorMask).isEqualTo(StatsEvent.ERROR_ATOM_ID_INVALID_POSITION);
642 
643         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
644 
645         statsEvent.release();
646     }
647 
648     @Test
testLargePulledEvent()649     public void testLargePulledEvent() {
650         final int expectedAtomId = 10_020;
651         byte[] field1 = new byte[10 * 1024];
652         new Random().nextBytes(field1);
653 
654         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
655         final StatsEvent statsEvent =
656                 StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build();
657         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
658 
659         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
660 
661         final ByteBuffer buffer =
662                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
663 
664         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
665                 .that(buffer.get())
666                 .isEqualTo(StatsEvent.TYPE_OBJECT);
667 
668         assertWithMessage("Incorrect number of elements in root object")
669                 .that(buffer.get())
670                 .isEqualTo(3);
671 
672         assertWithMessage("First element is not timestamp")
673                 .that(buffer.get())
674                 .isEqualTo(StatsEvent.TYPE_LONG);
675 
676         assertWithMessage("Incorrect timestamp")
677                 .that(buffer.getLong())
678                 .isIn(Range.closed(minTimestamp, maxTimestamp));
679 
680         assertWithMessage("Second element is not atom id")
681                 .that(buffer.get())
682                 .isEqualTo(StatsEvent.TYPE_INT);
683 
684         assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
685 
686         assertWithMessage("Third element is not byte array")
687                 .that(buffer.get())
688                 .isEqualTo(StatsEvent.TYPE_BYTE_ARRAY);
689 
690         final byte[] field1Actual = getByteArrayFromByteBuffer(buffer);
691         assertWithMessage("Incorrect field 1").that(field1Actual).isEqualTo(field1);
692 
693         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
694 
695         statsEvent.release();
696     }
697 
698     @Test
testPulledEventOverflow()699     public void testPulledEventOverflow() {
700         final int expectedAtomId = 10_020;
701         byte[] field1 = new byte[50 * 1024];
702         new Random().nextBytes(field1);
703 
704         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
705         final StatsEvent statsEvent =
706                 StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build();
707         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
708 
709         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
710 
711         final ByteBuffer buffer =
712                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
713 
714         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
715                 .that(buffer.get())
716                 .isEqualTo(StatsEvent.TYPE_OBJECT);
717 
718         assertWithMessage("Incorrect number of elements in root object")
719                 .that(buffer.get())
720                 .isEqualTo(3);
721 
722         assertWithMessage("First element is not timestamp")
723                 .that(buffer.get())
724                 .isEqualTo(StatsEvent.TYPE_LONG);
725 
726         assertWithMessage("Incorrect timestamp")
727                 .that(buffer.getLong())
728                 .isIn(Range.closed(minTimestamp, maxTimestamp));
729 
730         assertWithMessage("Second element is not atom id")
731                 .that(buffer.get())
732                 .isEqualTo(StatsEvent.TYPE_INT);
733 
734         assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
735 
736         assertWithMessage("Third element is not errors type")
737                 .that(buffer.get())
738                 .isEqualTo(StatsEvent.TYPE_ERRORS);
739 
740         final int errorMask = buffer.getInt();
741 
742         assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
743                 .that(errorMask)
744                 .isEqualTo(StatsEvent.ERROR_OVERFLOW);
745 
746         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
747 
748         statsEvent.release();
749     }
750 
751     @Test
testPushedEventOverflow()752     public void testPushedEventOverflow() {
753         final int expectedAtomId = 10_020;
754         byte[] field1 = new byte[10 * 1024];
755         new Random().nextBytes(field1);
756 
757         final long minTimestamp = SystemClock.elapsedRealtimeNanos();
758         final StatsEvent statsEvent = StatsEvent.newBuilder()
759                                               .setAtomId(expectedAtomId)
760                                               .writeByteArray(field1)
761                                               .usePooledBuffer()
762                                               .build();
763         final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
764 
765         assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId);
766 
767         final ByteBuffer buffer =
768                 ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN);
769 
770         assertWithMessage("Root element in buffer is not TYPE_OBJECT")
771                 .that(buffer.get())
772                 .isEqualTo(StatsEvent.TYPE_OBJECT);
773 
774         assertWithMessage("Incorrect number of elements in root object")
775                 .that(buffer.get())
776                 .isEqualTo(3);
777 
778         assertWithMessage("First element is not timestamp")
779                 .that(buffer.get())
780                 .isEqualTo(StatsEvent.TYPE_LONG);
781 
782         assertWithMessage("Incorrect timestamp")
783                 .that(buffer.getLong())
784                 .isIn(Range.closed(minTimestamp, maxTimestamp));
785 
786         assertWithMessage("Second element is not atom id")
787                 .that(buffer.get())
788                 .isEqualTo(StatsEvent.TYPE_INT);
789 
790         assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId);
791 
792         assertWithMessage("Third element is not errors type")
793                 .that(buffer.get())
794                 .isEqualTo(StatsEvent.TYPE_ERRORS);
795 
796         final int errorMask = buffer.getInt();
797 
798         assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask")
799                 .that(errorMask)
800                 .isEqualTo(StatsEvent.ERROR_OVERFLOW);
801 
802         assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position());
803 
804         statsEvent.release();
805     }
806 
getByteArrayFromByteBuffer(final ByteBuffer buffer)807     private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) {
808         final int numBytes = buffer.getInt();
809         byte[] bytes = new byte[numBytes];
810         buffer.get(bytes);
811         return bytes;
812     }
813 
getStringFromByteBuffer(final ByteBuffer buffer)814     private static String getStringFromByteBuffer(final ByteBuffer buffer) {
815         final byte[] bytes = getByteArrayFromByteBuffer(buffer);
816         return new String(bytes, UTF_8);
817     }
818 }
819