1 /* 2 * Copyright (C) 2022 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.adservices.service.js; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.junit.Assert.assertThrows; 27 28 import android.content.Context; 29 30 import androidx.javascriptengine.JavaScriptSandbox; 31 import androidx.test.core.app.ApplicationProvider; 32 import androidx.test.filters.SmallTest; 33 34 import com.android.adservices.LoggerFactory; 35 import com.android.adservices.service.profiling.JSScriptEngineLogConstants; 36 import com.android.adservices.service.profiling.Profiler; 37 import com.android.adservices.service.profiling.StopWatch; 38 import com.android.adservices.shared.testing.FutureSyncCallback; 39 import com.android.adservices.shared.testing.SdkLevelSupportRule; 40 import com.android.dx.mockito.inline.extended.ExtendedMockito; 41 import com.android.dx.mockito.inline.extended.StaticMockitoSession; 42 43 import com.google.common.util.concurrent.FluentFuture; 44 import com.google.common.util.concurrent.Futures; 45 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Rule; 49 import org.junit.Test; 50 import org.junit.function.ThrowingRunnable; 51 import org.mockito.Mock; 52 import org.mockito.Mockito; 53 54 import java.util.concurrent.ExecutionException; 55 import java.util.concurrent.TimeUnit; 56 import java.util.concurrent.TimeoutException; 57 58 @SmallTest 59 public class JavaScriptSandboxProviderTest { 60 private final Context mApplicationContext = ApplicationProvider.getApplicationContext(); 61 private final LoggerFactory.Logger mLogger = LoggerFactory.getFledgeLogger(); 62 private StaticMockitoSession mStaticMockSession; 63 @Mock private StopWatch mSandboxInitWatch; 64 @Mock private JavaScriptSandbox mSandbox; 65 @Mock private JavaScriptSandbox mSandbox2; 66 67 @Mock private Profiler mProfilerMock; 68 69 private JSScriptEngine.JavaScriptSandboxProvider mJsSandboxProvider; 70 71 @Rule(order = 0) 72 public final SdkLevelSupportRule sdkLevel = SdkLevelSupportRule.forAtLeastS(); 73 74 @Before setUp()75 public void setUp() { 76 mStaticMockSession = 77 ExtendedMockito.mockitoSession() 78 .mockStatic(JavaScriptSandbox.class) 79 .initMocks(this) 80 .startMocking(); 81 } 82 83 @After shutDown()84 public void shutDown() { 85 mStaticMockSession.finishMocking(); 86 } 87 88 @Test testJsSandboxProviderCreateFailsIfSandboxNotSupported()89 public void testJsSandboxProviderCreateFailsIfSandboxNotSupported() { 90 when(JavaScriptSandbox.isSupported()).thenReturn(false); 91 mJsSandboxProvider = new JSScriptEngine.JavaScriptSandboxProvider(mProfilerMock, mLogger); 92 ThrowingRunnable getFutureInstance = 93 () -> mJsSandboxProvider.getFutureInstance(mApplicationContext).get(); 94 Exception futureException = assertThrows(ExecutionException.class, getFutureInstance); 95 assertThat(futureException) 96 .hasCauseThat() 97 .isInstanceOf(JSSandboxIsNotAvailableException.class); 98 verify(JavaScriptSandbox::isSupported); 99 } 100 101 @Test testJsSandboxProviderCreatesOnlyOneInstance()102 public void testJsSandboxProviderCreatesOnlyOneInstance() 103 throws ExecutionException, InterruptedException, TimeoutException { 104 when(JavaScriptSandbox.isSupported()).thenReturn(true); 105 doReturn(Futures.immediateFuture(mSandbox)) 106 .when( 107 () -> { 108 return JavaScriptSandbox.createConnectedInstanceAsync( 109 mApplicationContext); 110 }); 111 112 when(mProfilerMock.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 113 .thenReturn(mSandboxInitWatch); 114 mJsSandboxProvider = new JSScriptEngine.JavaScriptSandboxProvider(mProfilerMock, mLogger); 115 116 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 117 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 118 119 verify(() -> JavaScriptSandbox.createConnectedInstanceAsync(mApplicationContext)); 120 verify(mProfilerMock).start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME); 121 } 122 123 @Test testJsSandboxProviderCreatesNewInstanceAfterFirstIsDestroyed()124 public void testJsSandboxProviderCreatesNewInstanceAfterFirstIsDestroyed() 125 throws ExecutionException, InterruptedException, TimeoutException { 126 when(JavaScriptSandbox.isSupported()).thenReturn(true); 127 doReturn(Futures.immediateFuture(mSandbox)) 128 .when( 129 () -> { 130 return JavaScriptSandbox.createConnectedInstanceAsync( 131 mApplicationContext); 132 }); 133 134 when(mProfilerMock.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 135 .thenReturn(mSandboxInitWatch); 136 mJsSandboxProvider = new JSScriptEngine.JavaScriptSandboxProvider(mProfilerMock, mLogger); 137 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 138 139 // Waiting for the first instance closure 140 mJsSandboxProvider.destroyCurrentInstance().get(4, TimeUnit.SECONDS); 141 142 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 143 144 verify( 145 () -> JavaScriptSandbox.createConnectedInstanceAsync(mApplicationContext), 146 Mockito.times(2)); 147 148 verify(mProfilerMock, Mockito.times(2)).start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME); 149 } 150 151 @Test testJsSandboxProviderDestroysOnlyIfCurrentInstance()152 public void testJsSandboxProviderDestroysOnlyIfCurrentInstance() 153 throws ExecutionException, InterruptedException, TimeoutException { 154 when(JavaScriptSandbox.isSupported()).thenReturn(true); 155 when(JavaScriptSandbox.createConnectedInstanceAsync(mApplicationContext)) 156 .thenReturn(Futures.immediateFuture(mSandbox)) 157 .thenReturn(Futures.immediateFuture(mSandbox2)); 158 doNothing().when(mSandbox).close(); 159 when(mProfilerMock.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 160 .thenReturn(mSandboxInitWatch); 161 162 mJsSandboxProvider = new JSScriptEngine.JavaScriptSandboxProvider(mProfilerMock, mLogger); 163 JavaScriptSandbox sandbox1 = 164 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 165 // Waiting for the first instance closure 166 mJsSandboxProvider.destroyIfCurrentInstance(sandbox1).get(4, TimeUnit.SECONDS); 167 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 168 mJsSandboxProvider.destroyIfCurrentInstance(sandbox1).get(4, TimeUnit.SECONDS); 169 170 verify(mSandbox).close(); 171 verify(mSandbox2, Mockito.never()).close(); 172 verify(mProfilerMock, Mockito.times(2)).start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME); 173 } 174 175 @Test testJsSandboxProviderDestroysOnlyIfCurrentInstanceOnlyOnce()176 public void testJsSandboxProviderDestroysOnlyIfCurrentInstanceOnlyOnce() 177 throws ExecutionException, InterruptedException, TimeoutException { 178 when(JavaScriptSandbox.isSupported()).thenReturn(true); 179 when(JavaScriptSandbox.createConnectedInstanceAsync(mApplicationContext)) 180 .thenReturn(Futures.immediateFuture(mSandbox)) 181 .thenThrow( 182 new IllegalStateException( 183 "createConnectedInstanceAsync should only be called once from the" 184 + " test")); 185 doNothing().when(mSandbox).close(); 186 when(mProfilerMock.start(JSScriptEngineLogConstants.SANDBOX_INIT_TIME)) 187 .thenReturn(mSandboxInitWatch); 188 189 mJsSandboxProvider = new JSScriptEngine.JavaScriptSandboxProvider(mProfilerMock, mLogger); 190 JavaScriptSandbox sandbox1 = 191 mJsSandboxProvider.getFutureInstance(mApplicationContext).get(5, TimeUnit.SECONDS); 192 FutureSyncCallback<Void> callback1 = new FutureSyncCallback<>(); 193 FutureSyncCallback<Void> callback2 = new FutureSyncCallback<>(); 194 FluentFuture.from(mJsSandboxProvider.destroyIfCurrentInstance(sandbox1)) 195 .addCallback(callback1, Runnable::run); 196 FluentFuture.from(mJsSandboxProvider.destroyIfCurrentInstance(sandbox1)) 197 .addCallback(callback2, Runnable::run); 198 199 callback1.assertResultReceived(); 200 callback2.assertResultReceived(); 201 verify(mSandbox).close(); 202 } 203 } 204