Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency updates 20200716 #6682

Merged
merged 4 commits into from
Jul 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 10 additions & 6 deletions AnkiDroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,14 @@ apply from: "./jacoco.gradle"
apply from: "../lint.gradle"

dependencies {
compileOnly 'org.jetbrains:annotations:16.0.1'
// Can possibly remove if upstream PR merges https://github.com/JakeWharton/timber/pull/398
configurations.all {
resolutionStrategy {
force 'org.jetbrains:annotations:19.0.0'
}
}

compileOnly 'org.jetbrains:annotations:19.0.0'
compileOnly "com.google.auto.service:auto-service-annotations:1.0-rc7"
annotationProcessor "com.google.auto.service:auto-service:1.0-rc7"

Expand All @@ -221,7 +228,7 @@ dependencies {
implementation 'androidx.sqlite:sqlite-framework:2.1.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'io.requery:sqlite-android:3.31.0'
implementation 'io.requery:sqlite-android:3.32.2'
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'org.bitbucket.cowwoc:diff-match-patch:1.2'
Expand All @@ -247,10 +254,7 @@ dependencies {
api project(":api")

testImplementation 'org.junit.vintage:junit-vintage-engine:5.6.2'
testImplementation 'org.mockito:mockito-core:3.3.3'
testImplementation 'org.powermock:powermock-core:2.0.7'
testImplementation 'org.powermock:powermock-module-junit4:2.0.7'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.7'
testImplementation 'org.mockito:mockito-inline:3.4.0'
testImplementation 'org.hamcrest:hamcrest-all:1.3'
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
testImplementation "org.robolectric:robolectric:4.3.1"
Expand Down
138 changes: 71 additions & 67 deletions AnkiDroid/src/test/java/com/ichi2/anki/AnalyticsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@PowerMockIgnore("javax.net.ssl.*")
@PrepareForTest({PreferenceManager.class, GoogleAnalytics.class})
@RunWith(PowerMockRunner.class)

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class AnalyticsTest {

@Mock
Expand All @@ -57,96 +57,100 @@ public class AnalyticsTest {
@Mock
private SharedPreferences.Editor mMockSharedPreferencesEditor;

// This is actually a Mockito Spy of GoogleAnalyticsImpl
private GoogleAnalytics mAnalytics;

@Before
public void setUp() {
PowerMockito.mockStatic(PreferenceManager.class);
PowerMockito.mockStatic(GoogleAnalytics.class);
MockitoAnnotations.openMocks(this);

MockitoAnnotations.initMocks(this);

Mockito.when(mMockResources.getBoolean(R.bool.ga_anonymizeIp))
when(mMockResources.getBoolean(R.bool.ga_anonymizeIp))
.thenReturn(true);
Mockito.when(mMockResources.getInteger(R.integer.ga_sampleFrequency))
when(mMockResources.getInteger(R.integer.ga_sampleFrequency))
.thenReturn(10);
Mockito.when(mMockContext.getResources())
when(mMockContext.getResources())
.thenReturn(mMockResources);

Mockito.when(mMockContext.getString(R.string.ga_trackingId))
when(mMockContext.getString(R.string.ga_trackingId))
.thenReturn("Mock Tracking ID");
Mockito.when(mMockContext.getString(R.string.app_name))
when(mMockContext.getString(R.string.app_name))
.thenReturn("Mock Application Name");
Mockito.when(mMockContext.getPackageName())
when(mMockContext.getPackageName())
.thenReturn("mock_context");
Mockito.when(mMockContext.getSharedPreferences("mock_context_preferences", Context.MODE_PRIVATE))
when(mMockContext.getSharedPreferences("mock_context_preferences", Context.MODE_PRIVATE))
.thenReturn(mMockSharedPreferences);

Mockito.when(mMockSharedPreferences.getBoolean(UsageAnalytics.ANALYTICS_OPTIN_KEY, false))
when(mMockSharedPreferences.getBoolean(UsageAnalytics.ANALYTICS_OPTIN_KEY, false))
.thenReturn(true);
Mockito.when(PreferenceManager.getDefaultSharedPreferences(ArgumentMatchers.any()))
.thenReturn(mMockSharedPreferences);


Mockito.when(mMockSharedPreferencesEditor.putBoolean(UsageAnalytics.ANALYTICS_OPTIN_KEY, true))
when(mMockSharedPreferencesEditor.putBoolean(UsageAnalytics.ANALYTICS_OPTIN_KEY, true))
.thenReturn(mMockSharedPreferencesEditor);

Mockito.when(mMockSharedPreferences.edit())
when(mMockSharedPreferences.edit())
.thenReturn(mMockSharedPreferencesEditor);

Mockito.when(GoogleAnalytics.builder())
.thenReturn(new SpyGoogleAnalyticsBuilder());

mAnalytics = UsageAnalytics.initialize(mMockContext);
}


private static class SpyGoogleAnalyticsBuilder extends GoogleAnalyticsBuilder {
public GoogleAnalytics build() {
GoogleAnalytics analytics = super.build();
return Mockito.spy(analytics);
return spy(analytics);
}
}


@After
public void validate() {
Mockito.validateMockitoUsage();
validateMockitoUsage();
}


@Test
public void testSendException() {

// no root cause
Exception exception = Mockito.mock(Exception.class);
Mockito.when(exception.getCause()).thenReturn(null);
Throwable cause = UsageAnalytics.getCause(exception);
Mockito.verify(exception).getCause();
Assert.assertEquals(exception, cause);

// a 3-exception chain inside the actual analytics call
Exception childException = Mockito.mock(Exception.class);
Mockito.when(childException.getCause()).thenReturn(null);
Mockito.when(childException.toString()).thenReturn("child exception toString()");
Exception parentException = Mockito.mock(Exception.class);
Mockito.when(parentException.getCause()).thenReturn(childException);
Exception grandparentException = Mockito.mock(Exception.class);
Mockito.when(grandparentException.getCause()).thenReturn(parentException);

// prepare analytics so we can inspect what happens
ExceptionHit spyHit = Mockito.spy(new ExceptionHit());
Mockito.doReturn(spyHit).when(mAnalytics).exception();

try {
UsageAnalytics.sendAnalyticsException(grandparentException, false);
} catch (Exception e) {
// do nothing - this is expected because UsageAnalytics isn't fully initialized
try (
MockedStatic<PreferenceManager> ignored = mockStatic(PreferenceManager.class);
MockedStatic<GoogleAnalytics> ignored1 = mockStatic(GoogleAnalytics.class)) {

when(PreferenceManager.getDefaultSharedPreferences(ArgumentMatchers.any()))
.thenReturn(mMockSharedPreferences);

when(GoogleAnalytics.builder())
.thenReturn(new SpyGoogleAnalyticsBuilder());

// This is actually a Mockito Spy of GoogleAnalyticsImpl
GoogleAnalytics mAnalytics = UsageAnalytics.initialize(mMockContext);

// no root cause
Exception exception = mock(Exception.class);
when(exception.getCause()).thenReturn(null);
Throwable cause = UsageAnalytics.getCause(exception);
verify(exception).getCause();
Assert.assertEquals(exception, cause);

// a 3-exception chain inside the actual analytics call
Exception childException = mock(Exception.class);
when(childException.getCause()).thenReturn(null);
when(childException.toString()).thenReturn("child exception toString()");
Exception parentException = mock(Exception.class);
when(parentException.getCause()).thenReturn(childException);
Exception grandparentException = mock(Exception.class);
when(grandparentException.getCause()).thenReturn(parentException);

// prepare analytics so we can inspect what happens
ExceptionHit spyHit = spy(new ExceptionHit());
doReturn(spyHit).when(mAnalytics).exception();

try {
UsageAnalytics.sendAnalyticsException(grandparentException, false);
} catch (Exception e) {
// do nothing - this is expected because UsageAnalytics isn't fully initialized
}
verify(grandparentException).getCause();
verify(parentException).getCause();
verify(childException).getCause();
verify(mAnalytics).exception();
verify(spyHit).exceptionDescription(ArgumentMatchers.anyString());
verify(spyHit).sendAsync();
Assert.assertEquals(spyHit.exceptionDescription(), "child exception toString()");
}
Mockito.verify(grandparentException).getCause();
Mockito.verify(parentException).getCause();
Mockito.verify(childException).getCause();
Mockito.verify(mAnalytics).exception();
Mockito.verify(spyHit).exceptionDescription(ArgumentMatchers.anyString());
Mockito.verify(spyHit).sendAsync();
Assert.assertEquals(spyHit.exceptionDescription(), "child exception toString()");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,92 @@

import android.util.Log;

import com.ichi2.testutils.AnkiAssert;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.MockedStatic;

import timber.log.Timber;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeast;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Log.class)
public class ProductionCrashReportingTreeTest {


@Before
public void setUp() {

// setup - simply instrument the class and do same log init as production
PowerMockito.mockStatic(Log.class);
Timber.plant(new AnkiDroidApp.ProductionCrashReportingTree());
}


@After
public void tearDown() {
Timber.uprootAll();
}


/**
* The Production logger ignores verbose and debug logs on purpose
* Make sure these ignored log levels are not passed to the platform logger
*/
@Test
public void testProductionDebugVerboseIgnored() {

// set up the platform log so that if anyone calls these 2 methods at all, it throws
Mockito.when(Log.v(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Verbose logging should have been ignored"));
Mockito.when(Log.d(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Debug logging should be ignored"));

// now call our wrapper - if it hits the platform logger it will throw
try {
Timber.v("verbose");
Timber.d("debug");
} catch (Exception e) {
Assert.fail("we were unable to log without exception?");
try (MockedStatic<Log> ignored = mockStatic(Log.class)) {
// set up the platform log so that if anyone calls these 2 methods at all, it throws
when(Log.v(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Verbose logging should have been ignored"));
when(Log.d(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Debug logging should be ignored"));
when(Log.i(anyString(), anyString(), any()))
.thenThrow(new RuntimeException("Info logging should throw!"));

// now call our wrapper - if it hits the platform logger it will throw
AnkiAssert.assertDoesNotThrow(() -> Timber.v("verbose"));
AnkiAssert.assertDoesNotThrow(() -> Timber.d("debug"));

try {
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
Timber.i("info");
Assert.fail("we should have gone to Log.i and thrown but did not? Testing mechanism failure.");
} catch (Exception e) {
// this means everything worked, we were counting on an exception
}
}

}


/**
* The levels that are fully logged have special "tag" behavior per-level
*
* <p>
* Info: always {@link AnkiDroidApp#TAG} as the logging tag
* Warn/Error: tag is LoggingClass.className()'s most specific dot-separated String subsection
*/
@Test
@SuppressWarnings("PMD.JUnitTestsShoudIncludAssert")
public void testProductionLogTag() {

// setUp() instrumented the static, now exercise it
Timber.i("info level message");
Timber.w("warn level message");
Timber.e("error level message");
try (MockedStatic<Log> autoClosed = mockStatic(Log.class)) {

// verify that info level had the constant tag
verifyStatic(Log.class, atLeast(1));
Log.i(AnkiDroidApp.TAG, "info level message", null);
// Now let's run through our API calls...
Timber.i("info level message");
Timber.w("warn level message");
Timber.e("error level message");

// verify Warn/Error has final part of calling class name to start the message
verifyStatic(Log.class, atLeast(1));
Log.w(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "warn level message", null);
verifyStatic(Log.class, atLeast(1));
Log.e(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "error level message", null);
// ...and make sure they hit the logger class post-processed correctly
AnkiAssert.assertDoesNotThrow(() ->
autoClosed.verify(() -> Log.i(AnkiDroidApp.TAG, "info level message", null)));
AnkiAssert.assertDoesNotThrow(() ->
autoClosed.verify(() -> Log.w(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "warn level message", null)));
AnkiAssert.assertDoesNotThrow(() ->
autoClosed.verify(() -> Log.e(AnkiDroidApp.TAG, this.getClass().getSimpleName() + "/ " + "error level message", null)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.MockitoAnnotations;

import static com.ichi2.testutils.AnkiAssert.assertDoesNotThrow;
import static com.ichi2.utils.FunctionalInterfaces.Supplier;
Expand All @@ -16,8 +15,6 @@
import static org.mockito.ArgumentMatchers.eq;

//Unknown issue: @CheckResult should provide warnings on this class when return value is unused, but doesn't.
//TODO: The preference mock is messy
@RunWith(PowerMockRunner.class)
public class PreferenceExtensionsTest {

private static Supplier<String> UNUSED_SUPPLIER = () -> { throw new UnexpectedException();};
Expand All @@ -40,6 +37,7 @@ private String getOrSetString(String key, Supplier<String> supplier) {

@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
Mockito.when(mMockReferences.contains(VALID_KEY)).thenReturn(true);
Mockito.when(mMockReferences.getString(eq(VALID_KEY), anyString())).thenReturn(VALID_RESULT);
Mockito.when(mMockReferences.edit()).thenReturn(mockEditor);
Expand Down