Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/reusable-lib-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,13 @@ jobs:
for RESULT_FILE in $(gsutil ls "${BUCKET_PATH}/*/test_result_1.xml" 2>/dev/null | grep -v "rerun"); do
gsutil cp "${RESULT_FILE}" "firebase_results/api_${LEVEL}_test_result.xml"
done
# Pass 2: merge rerun testcases into originals so check_retries detects flaky tests
for RESULT_FILE in $(gsutil ls "${BUCKET_PATH}/*/test_result_1.xml" 2>/dev/null | grep "rerun"); do
RERUN_TMP="firebase_results/api_${LEVEL}_rerun_tmp.xml"
ORIG_FILE="firebase_results/api_${LEVEL}_test_result.xml"
gsutil cp "${RESULT_FILE}" "${RERUN_TMP}"
python3 - "${ORIG_FILE}" "${RERUN_TMP}" "${ORIG_FILE}" << 'PYEOF'
fi
# Pass 2: merge rerun testcases into originals so check_retries detects flaky tests
for RESULT_FILE in $(gsutil ls "${BUCKET_PATH}/*/test_result_1.xml" 2>/dev/null | grep "rerun"); do
RERUN_TMP="firebase_results/api_${LEVEL}_rerun_tmp.xml"
ORIG_FILE="firebase_results/api_${LEVEL}_test_result.xml"
gsutil cp "${RESULT_FILE}" "${RERUN_TMP}"
python3 - "${ORIG_FILE}" "${RERUN_TMP}" "${ORIG_FILE}" << 'PYEOF'
import sys, xml.etree.ElementTree as ET
orig = ET.parse(sys.argv[1])
rerun = ET.parse(sys.argv[2])
Expand All @@ -236,9 +237,8 @@ jobs:
with open(sys.argv[3], 'w') as f:
f.write(ET.tostring(orig.getroot(), encoding='unicode'))
PYEOF
rm "${RERUN_TMP}"
done
fi
rm "${RERUN_TMP}"
done

# Copy all shard data for code coverage (only needed for one level)
if [ "$LEVEL" == "$PR_API_VERSION" ] ; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
* PhoneGap plugin to run javascript tests.
Expand All @@ -51,7 +52,7 @@ public class TestRunnerPlugin extends ForcePlugin {

// To synchronize with the tests
public final static BlockingQueue<Boolean> readyForTests = new ArrayBlockingQueue<Boolean>(1);
public final static BlockingQueue<TestResult> testResults = new ArrayBlockingQueue<TestResult>(1);
public final static BlockingQueue<TestResult> testResults = new LinkedBlockingQueue<TestResult>();

/**
* Supported plugin actions that the client can take.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,11 @@ public void signoutCurrentUser(Activity frontActivity, boolean showLoginPage) {
* @param reason The reason for the logout.
*/
public void signoutCurrentUser(Activity frontActivity, boolean showLoginPage, OAuth2.LogoutReason reason) {
SalesforceSDKManager.getInstance().logout(
getCurrentAccount(), frontActivity, showLoginPage, reason);
final Account currentAccount = getCurrentAccount();
if (currentAccount != null) {
SalesforceSDKManager.getInstance().logout(
currentAccount, frontActivity, showLoginPage, reason);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This production hardening actually came from CI crashing on API 37 when one of the tests called signoutCurrentUser with a null currentUser. The issue is a byproduct of the Android 17 MessageQueue rewrite. Yay for tests (unintentionally) catching real issues.

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -72,6 +73,8 @@ public static void runJSTestSuite(String jsSuite, Iterable<String> testNames, in

// Start main activity
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
TestRunnerPlugin.readyForTests.clear();
TestRunnerPlugin.testResults.clear();
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(instrumentation.getTargetContext(), SalesforceSDKManager.getInstance().getMainActivityClass().getName());
Expand All @@ -90,7 +93,12 @@ public static void runJSTestSuite(String jsSuite, Iterable<String> testNames, in
// Block until test completes or times out
TestResult result;
try {
result = TestRunnerPlugin.testResults.poll(timeout, TimeUnit.SECONDS);
result = pollForTestResult(
TestRunnerPlugin.testResults,
testName,
timeout,
TimeUnit.SECONDS
);
if (result == null) {
result = new TestResult(testName, false, "Timeout (" + timeout + " seconds) exceeded", timeout);
}
Expand All @@ -110,6 +118,28 @@ public static void runJSTestSuite(String jsSuite, Iterable<String> testNames, in
}
}

static TestResult pollForTestResult(
BlockingQueue<TestResult> results,
String expectedTestName,
long timeout,
TimeUnit timeUnit
) throws InterruptedException {
final long deadline = System.nanoTime() + timeUnit.toNanos(timeout);
long remainingNanos = deadline - System.nanoTime();
while (remainingNanos > 0) {
final TestResult result = results.poll(remainingNanos, TimeUnit.NANOSECONDS);
if (result == null || expectedTestName.equals(result.testName)) {
return result;
}
SalesforceHybridLogger.w(
TAG,
"Ignoring late result for " + result.testName + " while waiting for " + expectedTestName
);
remainingNanos = deadline - System.nanoTime();
}
return null;
}

/**
* Helper method: No longer actually run the javascript test; Instead,
* asserts based on saved results.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2026-present, salesforce.com, inc.
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
* the following disclaimer in the documentation and/or other materials provided with the distribution.
*
* Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.salesforce.androidsdk.phonegap;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.salesforce.androidsdk.phonegap.plugin.TestRunnerPlugin;
import com.salesforce.androidsdk.phonegap.plugin.TestRunnerPlugin.TestResult;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
public class JSTestCaseTest {

@After
public void tearDown() {
TestRunnerPlugin.testResults.clear();
}

@Test
public void testResultQueueAcceptsLateAndCurrentResults() {
TestResult lateResult = new TestResult("timedOutTest", true, "", 31);
TestResult currentResult = new TestResult("currentTest", true, "", 1);

assertTrue(TestRunnerPlugin.testResults.offer(lateResult));
assertTrue(TestRunnerPlugin.testResults.offer(currentResult));
}

@Test
public void pollForTestResultIgnoresLateResultFromTimedOutTest() throws InterruptedException {
LinkedBlockingQueue<TestResult> results = new LinkedBlockingQueue<>();
results.add(new TestResult("timedOutTest", true, "", 31));
results.add(new TestResult("currentTest", true, "", 1));

TestResult result = JSTestCase.pollForTestResult(
results,
"currentTest",
1,
TimeUnit.SECONDS
);

assertEquals("currentTest", result.testName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ public static List<String> data() {

@BeforeClass
public static void runJSTestSuite() throws InterruptedException {
JSTestCase.runJSTestSuite(JS_SUITE, data(), 60);
JSTestCase.runJSTestSuite(JS_SUITE, data(), 180);
}

@Test
public void test() {
runTest(JS_SUITE, testName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static List<String> data() {

@BeforeClass
public static void runJSTestSuite() throws InterruptedException {
JSTestCase.runJSTestSuite(JS_SUITE, data(), 30);
JSTestCase.runJSTestSuite(JS_SUITE, data(), 60);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static List<String> data() {

@BeforeClass
public static void runJSTestSuite() throws InterruptedException {
JSTestCase.runJSTestSuite(JS_SUITE, data(), 60);
JSTestCase.runJSTestSuite(JS_SUITE, data(), 180);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
* Tests for UserAccountManager.
Expand Down Expand Up @@ -389,6 +390,20 @@ public void testDoesUserAccountExist() {
Assert.assertTrue("User should exist now", userAccMgr.doesUserAccountExist(secondUser));
}

/**
* Test that signing out the current user is a no-op when there is no current account.
*/
@Test
public void testSignoutCurrentUserDoesNothingWithoutCurrentAccount() throws InterruptedException {
Assert.assertNull("There should be no current account", userAccMgr.getCurrentAccount());

userAccMgr.signoutCurrentUser(null, false, OAuth2.LogoutReason.USER_LOGOUT);

Assert.assertFalse(
"Logout completion should not be broadcast when there is no current account",
logoutCompleteReceiver.awaitCompletion(1, TimeUnit.SECONDS));
}

/**
* Test to signout of the current user.
*/
Expand Down Expand Up @@ -627,6 +642,10 @@ public UserAccount getLastUserAccountReceived() {
completionSemaphore.release();
return lastUserAccountReceived;
}

public boolean awaitCompletion(long timeout, TimeUnit timeUnit) throws InterruptedException {
return completionSemaphore.tryAcquire(timeout, timeUnit);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class NativeLoginManagerTest {
@After
fun tearDown() {
realUserAccountManager.signoutCurrentUser(null, false, OAuth2.LogoutReason.USER_LOGOUT)
instrumentation.waitForIdleSync()
activityMonitors.forEach(instrumentation::removeMonitor)
unmockkAll()
}
Expand Down
Loading