blob: b34e29456d617fa02d97800e2ed01187b19c4e17 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.internal;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import com.android.annotations.NonNull;
import com.android.testutils.classloader.MultiClassLoader;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.gradle.BuildListener;
import org.gradle.BuildResult;
import org.gradle.api.invocation.Gradle;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
/** Test cases for {@link BuildSessionImpl}. */
@SuppressWarnings("FieldCanBeLocal")
public class BuildSessionImplTest {
// Note: Since the tested class uses a global object (BuildSession), we need to use fake plugin
// versions to avoid potential conflicts with integration tests which could be running in
// parallel.
@NonNull private final String fakePluginVersion = "1.2.3";
@NonNull private final String fakePluginVersion2 = "a.b.c";
@Test
public void testCreateBuildSessionSingleton() throws Exception {
// Create the BuildSessionImpl singleton object
BuildSession singleton = BuildSessionImpl.createBuildSessionSingleton(fakePluginVersion);
assertThat(singleton).isInstanceOf(BuildSessionImpl.class);
// If we get the singleton again for the same plugin version, it must return the same object
BuildSession sameSingleton =
BuildSessionImpl.createBuildSessionSingleton(fakePluginVersion);
assertThat(sameSingleton).isSameAs(singleton);
// If we get the singleton again for a different plugin version, it must return a different
// object
BuildSession differentSingleton =
BuildSessionImpl.createBuildSessionSingleton(fakePluginVersion2);
assertThat(differentSingleton).isInstanceOf(BuildSessionImpl.class);
assertThat(differentSingleton).isNotSameAs(singleton);
// If we get the singleton again for the same plugin version, but from a different class
// loader, it must return a proxy to the actual singleton
MultiClassLoader classLoader =
new MultiClassLoader(
ImmutableList.of(
BuildSession.class.getName(), BuildSessionImpl.class.getName()));
List<Class<?>> classes = classLoader.load();
Class<?> buildSessionInterface = classes.get(0);
Class<?> buildSessionImplClass = classes.get(1);
assertThat(buildSessionInterface.getClassLoader())
.isNotSameAs(BuildSession.class.getClassLoader());
assertThat(buildSessionImplClass.getClassLoader())
.isNotSameAs(BuildSessionImpl.class.getClassLoader());
Method createBuildSessionSingletonMethod =
buildSessionImplClass.getDeclaredMethod(
"createBuildSessionSingleton", String.class);
createBuildSessionSingletonMethod.setAccessible(true);
Object proxyToSingleton = createBuildSessionSingletonMethod.invoke(null, fakePluginVersion);
assertThat(proxyToSingleton).isInstanceOf(buildSessionInterface);
assertThat(proxyToSingleton).isNotInstanceOf(buildSessionImplClass);
assertThat(proxyToSingleton).isNotSameAs(singleton);
Object delegateInvocationHandler = Proxy.getInvocationHandler(proxyToSingleton);
//noinspection JavaReflectionMemberAccess
Method getDelegateMethod = delegateInvocationHandler.getClass().getMethod("getDelegate");
getDelegateMethod.setAccessible(true);
Object anotherSameSingleton = getDelegateMethod.invoke(delegateInvocationHandler);
assertThat(anotherSameSingleton).isSameAs(singleton);
}
@Test
public void testExecuteOnce() {
// Create the BuildSessionImpl singleton object
BuildSession singleton = BuildSessionImpl.createBuildSessionSingleton(fakePluginVersion);
// Simulate starting a build
Gradle gradle = mock(Gradle.class);
singleton.initialize(gradle);
ArgumentCaptor<BuildListener> captor = ArgumentCaptor.forClass(BuildListener.class);
verify(gradle).addBuildListener(captor.capture());
BuildListener buildListener = captor.getValue();
// Execute an action immediately
AtomicInteger counter = new AtomicInteger(0);
Runnable increaseCounter = counter::incrementAndGet;
singleton.executeOnce(
BuildSessionImplTest.class.getName(), "increaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(1);
// Execute the same action but with the same action name, it should not be executed
singleton.executeOnce(
BuildSessionImplTest.class.getName(), "increaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(1);
// Execute the same action but with a different action name, it should be executed
singleton.executeOnce(
BuildSessionImplTest.class.getName(), "alsoIncreaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(2);
// Execute a different action but with the same action name, it should not be executed
singleton.executeOnce(
BuildSessionImplTest.class.getName(),
"increaseCounter",
() -> counter.getAndAdd(4));
assertThat(counter.get()).isEqualTo(2);
// Execute a different action with a different action name, it should be executed
singleton.executeOnce(
BuildSessionImplTest.class.getName(),
"increaseCounterBy4",
() -> counter.getAndAdd(8));
assertThat(counter.get()).isEqualTo(10);
// Let the build finish
buildListener.buildFinished(new BuildResult(null, null));
assertThat(counter.get()).isEqualTo(10);
// Check that the same action and action name can be executed again in the next build (i.e.,
// the builds should be independent)
singleton.initialize(gradle);
singleton.executeOnce(
BuildSessionImplTest.class.getName(), "increaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(11);
buildListener.buildFinished(new BuildResult(null, null));
assertThat(counter.get()).isEqualTo(11);
}
@Test
public void testExecuteOnceWhenBuildFinished() {
// Create the BuildSessionImpl singleton object
BuildSession singleton = BuildSessionImpl.createBuildSessionSingleton(fakePluginVersion);
// Simulate starting a build
Gradle gradle = mock(Gradle.class);
singleton.initialize(gradle);
ArgumentCaptor<BuildListener> captor = ArgumentCaptor.forClass(BuildListener.class);
verify(gradle).addBuildListener(captor.capture());
BuildListener buildListener = captor.getValue();
// Register an action to be executed at the end of the build, it should be executed later
AtomicInteger counter = new AtomicInteger(0);
Runnable increaseCounter = counter::incrementAndGet;
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(), "increaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(0);
// Register the same action but with the same action name, it should not be executed later
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(), "increaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(0);
// Register the same action but with a different action name, it should be executed later
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(), "alsoIncreaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(0);
// Register a different action but with the same action name, it should not be executed
// later
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(),
"increaseCounter",
() -> counter.getAndAdd(4));
assertThat(counter.get()).isEqualTo(0);
// Register a different action with a different action name, it should be executed later
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(),
"increaseCounterBy4",
() -> counter.getAndAdd(8));
assertThat(counter.get()).isEqualTo(0);
// Let the build finish, now the actions should be executed
buildListener.buildFinished(new BuildResult(null, null));
assertThat(counter.get()).isEqualTo(10);
// Check that the same action and action name can be executed again in the next build, and
// the actions registered in the previous build are not re-executed in the next build (i.e.,
// the builds should be independent)
singleton.initialize(gradle);
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(), "increaseCounter", increaseCounter);
assertThat(counter.get()).isEqualTo(10);
buildListener.buildFinished(new BuildResult(null, null));
assertThat(counter.get()).isEqualTo(11);
}
@Test
public void testIgnoreCorruptedBuildState() {
// Create the BuildSessionImpl singleton object
BuildSession singleton = BuildSessionImpl.createBuildSessionSingleton(fakePluginVersion);
// Simulate starting a build
Gradle gradle = mock(Gradle.class);
singleton.initialize(gradle);
ArgumentCaptor<BuildListener> captor = ArgumentCaptor.forClass(BuildListener.class);
verify(gradle).addBuildListener(captor.capture());
BuildListener buildListener = captor.getValue();
// Register an action to be executed when the build is finished, let that action throw an
// exception
singleton.executeOnceWhenBuildFinished(
BuildSessionImplTest.class.getName(),
"actionThrowingException",
() -> {
throw new RuntimeException("Some error");
});
// Let the build finish, an exception should be thrown and build state should be corrupted
try {
buildListener.buildFinished(new BuildResult(null, null));
fail("Expected RuntimeException");
} catch (RuntimeException e) {
assertThat(e).hasMessage("Some error");
}
// Check that the next build ignores this corrupted state
singleton.initialize(gradle);
buildListener.buildFinished(new BuildResult(null, null));
}
}