1 /*
2  * Copyright (C) 2020 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 package com.android.tradefed.invoker.logger;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.junit.runners.JUnit4;
23 
24 import com.google.common.collect.ImmutableSet;
25 import com.google.common.util.concurrent.Futures;
26 
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.Future;
31 import java.util.concurrent.atomic.AtomicInteger;
32 
33 /** Unit tests for {@link InvocationLocal}. */
34 @RunWith(JUnit4.class)
35 public final class InvocationLocalTest {
36 
37     private final AtomicInteger nextInvocationId = new AtomicInteger();
38 
39     @Test
getInitialValueDefaultsToNull()40     public void getInitialValueDefaultsToNull() {
41         InvocationLocal<Integer> local = new InvocationLocal<>();
42 
43         Object actual = invocation(() -> local.get());
44 
45         assertThat(actual).isNull();
46     }
47 
48     @Test
getReturnsCustomInitialValue()49     public void getReturnsCustomInitialValue() {
50         String expected = "!";
51         InvocationLocal<String> local =
52                 new InvocationLocal<String>() {
53                     @Override
54                     protected String initialValue() {
55                         return expected;
56                     }
57                 };
58 
59         String actual = invocation(() -> local.get());
60 
61         assertThat(actual).isEqualTo(expected);
62     }
63 
64     @Test
getReturnsSameInitialValue()65     public void getReturnsSameInitialValue() {
66         InvocationLocal<Object> local =
67                 new InvocationLocal<Object>() {
68                     @Override
69                     protected Object initialValue() {
70                         return new Object();
71                     }
72                 };
73 
74         ImmutableSet<Object> values = invocation(() -> ImmutableSet.of(local.get(), local.get()));
75 
76         assertThat(values).hasSize(1);
77     }
78 
79     @Test
getReturnsDifferentValuePerInvocation()80     public void getReturnsDifferentValuePerInvocation() throws Exception {
81         InvocationLocal<Object> local =
82                 new InvocationLocal<Object>() {
83                     @Override
84                     protected Object initialValue() {
85                         return new Object();
86                     }
87                 };
88 
89         Object value0 = invocation(() -> local.get());
90         Object value1 = invocation(() -> local.get());
91 
92         assertThat(value0).isNotSameAs(value1);
93     }
94 
95     /**
96      * Runs code in the context of a ThreadGroup to simulate a Tradefed invocation and avoid
97      * interfering with the currently executing test invocation.
98      */
invocation(Callable<T> callable)99     private <T> T invocation(Callable<T> callable) {
100         // TradeFed Invocations are identified by their ThreadGroup.
101         String name = "invocation" + nextInvocationId.getAndIncrement();
102         ExecutorService executor =
103                 Executors.newSingleThreadExecutor(r -> new Thread(new ThreadGroup(name), r));
104         Future<T> future =
105                 executor.submit(
106                         () -> {
107                             try {
108                                 return callable.call();
109                             } finally {
110                                 CurrentInvocation.clearInvocationInfos();
111                             }
112                         });
113         executor.shutdown();
114         return Futures.getUnchecked(future);
115     }
116 }
117