Skip to content

Commit e5b5fce

Browse files
committed
Add stress test to msaltestapp
1 parent 95e5aed commit e5b5fce

File tree

5 files changed

+366
-4
lines changed

5 files changed

+366
-4
lines changed

common

Submodule common updated 25 files

testapps/testapp/src/main/java/com/microsoft/identity/client/testapp/AcquireTokenFragment.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565

6666
import java.util.ArrayList;
6767
import java.util.List;
68+
import java.util.Locale;
6869

6970
import io.opentelemetry.api.trace.Span;
7071
import io.opentelemetry.api.trace.StatusCode;
@@ -122,6 +123,15 @@ public class AcquireTokenFragment extends Fragment {
122123
private MsalWrapper mMsalWrapper;
123124
private List<IAccount> mLoadedAccounts = new ArrayList<>();
124125

126+
// Concurrent execution UI elements
127+
private EditText mConcurrentCount;
128+
private EditText mConcurrentTotalCount;
129+
private Button mRunConcurrent;
130+
private Button mStopConcurrent;
131+
private LinearLayout mThreadProgressContainer;
132+
private List<TextView> mThreadProgressViews = new ArrayList<>();
133+
private List<ConcurrentAcquireTokenExecutor> mRunningExecutors = new ArrayList<>();
134+
125135
private IClientActiveBrokerCache mCache;
126136
public AcquireTokenFragment() {
127137
// left empty
@@ -434,6 +444,27 @@ public void onCheckedChanged(CompoundButton v, boolean debugBrokers) {
434444
mMsalWrapper.getPreferredAuthMethod()
435445
));
436446

447+
// Initialize concurrent execution UI elements
448+
mConcurrentCount = view.findViewById(R.id.concurrent_count);
449+
mConcurrentTotalCount = view.findViewById(R.id.concurrent_total_count);
450+
mRunConcurrent = view.findViewById(R.id.btn_run_concurrent);
451+
mStopConcurrent = view.findViewById(R.id.btn_stop_concurrent);
452+
mThreadProgressContainer = view.findViewById(R.id.concurrent_thread_progress_container);
453+
454+
mRunConcurrent.setOnClickListener(new View.OnClickListener() {
455+
@Override
456+
public void onClick(View v) {
457+
runConcurrentAcquireTokenSilent();
458+
}
459+
});
460+
461+
mStopConcurrent.setOnClickListener(new View.OnClickListener() {
462+
@Override
463+
public void onClick(View v) {
464+
stopConcurrentExecutors();
465+
}
466+
});
467+
437468
return view;
438469
}
439470

@@ -694,6 +725,76 @@ public void run() {
694725
});
695726
}
696727

728+
private void runConcurrentAcquireTokenSilent() {
729+
try {
730+
final int concurrency = Integer.parseInt(mConcurrentCount.getText().toString());
731+
final int totalCount = Integer.parseInt(mConcurrentTotalCount.getText().toString());
732+
733+
if (concurrency <= 0 || totalCount <= 0) {
734+
showMessage("Concurrency and total count must be greater than 0");
735+
return;
736+
}
737+
738+
// Calculate requests per thread
739+
final int requestsPerThread = totalCount / concurrency;
740+
final int remainder = totalCount % concurrency;
741+
742+
// Reset progress
743+
mThreadProgressContainer.removeAllViews();
744+
mThreadProgressViews.clear();
745+
746+
// Create per-thread progress views
747+
for (int i = 0; i < concurrency; i++) {
748+
TextView threadProgress = new TextView(getContext());
749+
threadProgress.setText("Thread " + i + ": 0/0");
750+
threadProgress.setTextSize(12);
751+
threadProgress.setPadding(0, 5, 0, 5);
752+
mThreadProgressContainer.addView(threadProgress);
753+
mThreadProgressViews.add(threadProgress);
754+
}
755+
756+
// Create and start one executor per thread
757+
for (int i = 0; i < concurrency; i++) {
758+
final int threadId = i;
759+
final int countForThisThread = requestsPerThread + (i < remainder ? 1 : 0);
760+
761+
final ConcurrentAcquireTokenExecutor executor =
762+
new ConcurrentAcquireTokenExecutor(threadId, countForThisThread);
763+
764+
executor.execute(
765+
getContext(),
766+
getCurrentRequestOptions(),
767+
new ConcurrentAcquireTokenExecutor.IUIUpdateCallback() {
768+
@Override
769+
public void updateProgress(final int tid, final int successCount, final int completedCount) {
770+
if (tid >= 0 && tid < mThreadProgressViews.size()) {
771+
mThreadProgressViews.get(tid).setText(
772+
String.format(Locale.US,
773+
"Thread %d: %d/%d", tid, successCount, completedCount));
774+
}
775+
}
776+
777+
@Override
778+
public void onStopped(final int tid) {
779+
}
780+
}
781+
);
782+
783+
mRunningExecutors.add(executor);
784+
}
785+
} catch (NumberFormatException e) {
786+
showMessage("Please enter valid numbers for concurrency and total count");
787+
}
788+
}
789+
790+
private void stopConcurrentExecutors() {
791+
for (ConcurrentAcquireTokenExecutor executor : mRunningExecutors) {
792+
executor.stop();
793+
}
794+
mRunningExecutors.clear();
795+
showMessage("Stopped all running tasks");
796+
}
797+
697798
public interface OnFragmentInteractionListener {
698799
void onGetAuthResult(IAuthenticationResult result);
699800

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.client.testapp
24+
25+
import android.content.Context
26+
import android.os.Handler
27+
import android.os.Looper
28+
import com.microsoft.identity.client.IAuthenticationResult
29+
import java.util.concurrent.ExecutorService
30+
import java.util.concurrent.Executors
31+
import java.util.concurrent.atomic.AtomicBoolean
32+
33+
class ConcurrentAcquireTokenExecutor(
34+
val threadId: Int,
35+
val totalCount: Int
36+
) {
37+
38+
private val randomDelayInMs = (10..50).random().toLong()
39+
40+
// Flag to track if execution should stop
41+
private val isStopped = AtomicBoolean(false)
42+
43+
// Use a dedicated executor for background work
44+
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
45+
46+
interface IUIUpdateCallback {
47+
fun updateProgress(threadId: Int, successCount: Int, completedCount: Int)
48+
fun onStopped(threadId: Int)
49+
}
50+
51+
fun execute(context: Context,
52+
requestOptions: RequestOptions,
53+
uiCallback: IUIUpdateCallback){
54+
// MsalWrapper.create callbacks run on main thread, so call it directly
55+
MsalWrapper.create(
56+
context.applicationContext,
57+
Constants.getResourceIdFromConfigFile(requestOptions.configFile),
58+
object : INotifyOperationResultCallback<MsalWrapper?> {
59+
override fun onSuccess(result: MsalWrapper?) {
60+
// Start the first iteration immediately (no initial sleep)
61+
executeAcquireTokenSilent(result!!,
62+
0,
63+
0,
64+
requestOptions,
65+
uiCallback)
66+
}
67+
68+
override fun showMessage(message: String?) {
69+
// do nothing.
70+
}
71+
}
72+
)
73+
}
74+
75+
/**
76+
* Stop the executor and cancel any pending operations
77+
*/
78+
fun stop() {
79+
if (isStopped.compareAndSet(false, true)) {
80+
executor.shutdownNow()
81+
}
82+
}
83+
84+
private fun executeAcquireTokenSilent(msalWrapper: MsalWrapper,
85+
successCount: Int,
86+
completedCount: Int,
87+
requestOptions: RequestOptions,
88+
uiCallback: IUIUpdateCallback) {
89+
msalWrapper.acquireTokenSilent(
90+
requestOptions,
91+
object : INotifyOperationResultCallback<IAuthenticationResult> {
92+
override fun onSuccess(result: IAuthenticationResult) {
93+
val newSuccessCount = successCount + 1
94+
val newCompletedCount = completedCount + 1
95+
96+
// MSAL callbacks already run on main thread - update UI directly
97+
uiCallback.updateProgress(threadId, newSuccessCount, newCompletedCount)
98+
executeNext(
99+
msalWrapper,
100+
newSuccessCount,
101+
newCompletedCount,
102+
requestOptions,
103+
uiCallback
104+
)
105+
}
106+
107+
override fun showMessage(message: String?) {
108+
val newCompletedCount = completedCount + 1
109+
110+
// MSAL callbacks already run on main thread - update UI directly
111+
uiCallback.updateProgress(threadId, successCount, newCompletedCount)
112+
113+
executeNext(
114+
msalWrapper,
115+
successCount,
116+
newCompletedCount,
117+
requestOptions,
118+
uiCallback
119+
)
120+
}
121+
}
122+
)
123+
}
124+
125+
private fun executeNext(
126+
msalWrapper: MsalWrapper,
127+
newSuccessCount: Int,
128+
newCompletedCount: Int,
129+
requestOptions: RequestOptions,
130+
uiCallback: IUIUpdateCallback
131+
) {
132+
// Check if stopped
133+
if (isStopped.get()) {
134+
Handler(Looper.getMainLooper()).post {
135+
uiCallback.onStopped(threadId)
136+
}
137+
return
138+
}
139+
140+
if (newCompletedCount < totalCount) {
141+
executor.execute {
142+
try {
143+
Thread.sleep(randomDelayInMs)
144+
145+
// Post back to main thread to call acquireTokenSilent
146+
// (required since MSAL needs to be called from main thread)
147+
Handler(Looper.getMainLooper()).post {
148+
executeAcquireTokenSilent(
149+
msalWrapper,
150+
newSuccessCount,
151+
newCompletedCount,
152+
requestOptions,
153+
uiCallback
154+
)
155+
}
156+
} catch (e: InterruptedException) {
157+
// Interrupted - likely due to stop request
158+
Handler(Looper.getMainLooper()).post {
159+
uiCallback.onStopped(threadId)
160+
}
161+
}
162+
}
163+
}
164+
}
165+
166+
fun shutdown() {
167+
stop()
168+
}
169+
}

testapps/testapp/src/main/java/com/microsoft/identity/client/testapp/RequestOptions.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
@Getter
3636
@Accessors(prefix = "m")
3737
@AllArgsConstructor
38-
class RequestOptions {
38+
public class RequestOptions {
3939
private final Constants.ConfigFile mConfigFile;
4040
private final String mLoginHint;
4141
private final IAccount mAccount;
@@ -52,4 +52,8 @@ class RequestOptions {
5252
private final String mPopResourceUrl;
5353
private final String mPoPClientClaims;
5454
private final boolean mAllowSignInFromOtherDevice;
55+
56+
public Constants.ConfigFile getConfigFile(){
57+
return mConfigFile;
58+
}
5559
}

0 commit comments

Comments
 (0)