diff options
7 files changed, 260 insertions, 35 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index e7de1ca4f..59d72d30f 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -4917,7 +4917,7 @@ public class ClientModeImpl extends StateMachine { public void enter() { mRssiPollToken++; if (mEnableRssiPolling) { - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnNewConnection(); sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0); } if (mNetworkAgent != null) { @@ -5139,7 +5139,7 @@ public class ClientModeImpl extends StateMachine { if (mEnableRssiPolling) { // First poll mLastSignalLevel = -1; - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnScreenTurnedOn(); fetchRssiLinkSpeedAndFrequencyNative(); sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), mPollRssiIntervalMsecs); diff --git a/service/java/com/android/server/wifi/LinkProbeManager.java b/service/java/com/android/server/wifi/LinkProbeManager.java index ff26991f1..b91ba67f4 100644 --- a/service/java/com/android/server/wifi/LinkProbeManager.java +++ b/service/java/com/android/server/wifi/LinkProbeManager.java @@ -30,6 +30,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; /** * Tracks state that decides if a link probe should be performed. If so, trigger a link probe to @@ -48,6 +50,10 @@ public class LinkProbeManager { @VisibleForTesting static final long LINK_PROBE_INTERVAL_MS = 15 * 1000; + @VisibleForTesting + static final int[] EXPERIMENT_DELAYS_MS = {3000, 6000, 9000, 12000, 15000}; + private List<Experiment> mExperiments = new ArrayList<>(); + private final Clock mClock; private final WifiNative mWifiNative; private final WifiMetrics mWifiMetrics; @@ -63,7 +69,7 @@ public class LinkProbeManager { /** * Tracks the last timestamp when wifiInfo.txSuccess was increased i.e. the last time a Tx was * successful. Link probing only occurs when at least {@link #LINK_PROBE_INTERVAL_MS} has passed - * since the last Tx succcess. + * since the last Tx success. * This is also reset to the current time when {@link #reset()} is called, so that a link probe * only occurs at least {@link #LINK_PROBE_INTERVAL_MS} after a new connection is made. */ @@ -79,19 +85,22 @@ public class LinkProbeManager { mContext = context; mLinkProbingSupported = mContext.getResources() .getBoolean(R.bool.config_wifi_link_probing_supported); - if (!mLinkProbingSupported) return; - mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor( - Settings.Global.WIFI_LINK_PROBING_ENABLED), false, - new ContentObserver(new Handler(looper)) { - @Override - public void onChange(boolean selfChange) { - updateLinkProbeSetting(); - } - }); - updateLinkProbeSetting(); + if (mLinkProbingSupported) { + mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor( + Settings.Global.WIFI_LINK_PROBING_ENABLED), false, + new ContentObserver(new Handler(looper)) { + @Override + public void onChange(boolean selfChange) { + updateLinkProbeSetting(); + } + }); + updateLinkProbeSetting(); - reset(); + reset(); + } + + initExperiments(); } private void updateLinkProbeSetting() { @@ -117,11 +126,7 @@ public class LinkProbeManager { pw.println("LinkProbeManager - mLastTxSuccessCount: " + mLastTxSuccessCount); } - /** - * When connecting to a different network or when RSSI poll events are stopped and restarted, - * reset internal state. - */ - public void reset() { + private void reset() { if (!mLinkProbingSupported) return; long now = mClock.getElapsedSinceBootMillis(); @@ -131,6 +136,23 @@ public class LinkProbeManager { } /** + * When connecting to a new network, reset internal state. + */ + public void resetOnNewConnection() { + mExperiments.forEach(Experiment::resetOnNewConnection); + reset(); + } + + /** + * When RSSI poll events are stopped and restarted (usually screen turned off then back on), + * reset internal state. + */ + public void resetOnScreenTurnedOn() { + mExperiments.forEach(Experiment::resetOnScreenTurnedOn); + reset(); + } + + /** * Based on network conditions provided by WifiInfo, decides if a link probe should be * performed. If so, trigger a link probe and report the results to WifiMetrics. * @@ -138,6 +160,8 @@ public class LinkProbeManager { * @param interfaceName the interface that the link probe should be performed on, if applicable. */ public void updateConnectionStats(WifiInfo wifiInfo, String interfaceName) { + mExperiments.forEach(e -> e.updateConnectionStats(wifiInfo)); + if (!mLinkProbingSupported) return; long now = mClock.getElapsedSinceBootMillis(); @@ -209,4 +233,98 @@ public class LinkProbeManager { mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); } + + private void initExperiments() { + for (int screenOnDelayMs : EXPERIMENT_DELAYS_MS) { + for (int noTxDelayMs : EXPERIMENT_DELAYS_MS) { + for (int delayBetweenProbesMs : EXPERIMENT_DELAYS_MS) { + Experiment experiment = new Experiment(mClock, mWifiMetrics, + screenOnDelayMs, noTxDelayMs, delayBetweenProbesMs); + mExperiments.add(experiment); + } + } + } + } + + // TODO(b/131091030): remove once experiment is over + private static class Experiment { + + private final Clock mClock; + private final WifiMetrics mWifiMetrics; + private final int mScreenOnDelayMs; + private final int mNoTxDelayMs; + private final int mDelayBetweenProbesMs; + private final String mExperimentId; + + private long mLastLinkProbeTimestampMs; + private long mLastTxSuccessIncreaseTimestampMs; + private long mLastTxSuccessCount; + + Experiment(Clock clock, WifiMetrics wifiMetrics, + int screenOnDelayMs, int noTxDelayMs, int delayBetweenProbesMs) { + mClock = clock; + mWifiMetrics = wifiMetrics; + mScreenOnDelayMs = screenOnDelayMs; + mNoTxDelayMs = noTxDelayMs; + mDelayBetweenProbesMs = delayBetweenProbesMs; + + mExperimentId = getExperimentId(); + + resetOnNewConnection(); + } + + private String getExperimentId() { + return "[screenOnDelay=" + mScreenOnDelayMs + ',' + + "noTxDelay=" + mNoTxDelayMs + ',' + + "delayBetweenProbes=" + mDelayBetweenProbesMs + ']'; + } + + void resetOnNewConnection() { + long now = mClock.getElapsedSinceBootMillis(); + mLastLinkProbeTimestampMs = now; + mLastTxSuccessIncreaseTimestampMs = now; + mLastTxSuccessCount = 0; + } + + void resetOnScreenTurnedOn() { + long now = mClock.getElapsedSinceBootMillis(); + long firstPossibleLinkProbeAfterScreenOnTimestampMs = now + mScreenOnDelayMs; + mLastLinkProbeTimestampMs = Math.max(mLastLinkProbeTimestampMs, + firstPossibleLinkProbeAfterScreenOnTimestampMs - mDelayBetweenProbesMs); + // don't reset mLastTxSuccessIncreaseTimestampMs and mLastTxSuccessCount since no new + // connection was established + } + + void updateConnectionStats(WifiInfo wifiInfo) { + long now = mClock.getElapsedSinceBootMillis(); + + if (mLastTxSuccessCount < wifiInfo.txSuccess) { + mLastTxSuccessIncreaseTimestampMs = now; + } + mLastTxSuccessCount = wifiInfo.txSuccess; + + long timeSinceLastLinkProbeMs = now - mLastLinkProbeTimestampMs; + if (timeSinceLastLinkProbeMs < mDelayBetweenProbesMs) { + return; + } + + // if tx succeeded at least once in the last LINK_PROBE_INTERVAL_MS, don't need to probe + long timeSinceLastTxSuccessIncreaseMs = now - mLastTxSuccessIncreaseTimestampMs; + if (timeSinceLastTxSuccessIncreaseMs < mNoTxDelayMs) { + return; + } + + // can skip probing if RSSI is valid and high and link speed is fast + int rssi = wifiInfo.getRssi(); + int linkSpeed = wifiInfo.getLinkSpeed(); + if (rssi != WifiInfo.INVALID_RSSI && rssi > LINK_PROBE_RSSI_THRESHOLD + && linkSpeed > LINK_PROBE_LINK_SPEED_THRESHOLD_MBPS) { + return; + } + + mWifiMetrics.incrementLinkProbeExperimentProbeCount(mExperimentId); + + mLastLinkProbeTimestampMs = mClock.getElapsedSinceBootMillis(); + } + } } diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java index 8d6a5f1bd..539fe3dee 100644 --- a/service/java/com/android/server/wifi/WifiMetrics.java +++ b/service/java/com/android/server/wifi/WifiMetrics.java @@ -60,6 +60,7 @@ import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificatio import com.android.server.wifi.nano.WifiMetricsProto.DeviceMobilityStatePnoScanStats; import com.android.server.wifi.nano.WifiMetricsProto.ExperimentValues; import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats; +import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats.ExperimentProbeCounts; import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats.LinkProbeFailureReasonCount; import com.android.server.wifi.nano.WifiMetricsProto.LinkSpeedCount; import com.android.server.wifi.nano.WifiMetricsProto.NetworkSelectionExperimentDecisions; @@ -86,6 +87,7 @@ import com.android.server.wifi.util.InformationElementUtil; import com.android.server.wifi.util.IntCounter; import com.android.server.wifi.util.IntHistogram; import com.android.server.wifi.util.MetricsUtils; +import com.android.server.wifi.util.ObjectCounter; import com.android.server.wifi.util.ScanResultUtil; import org.json.JSONArray; @@ -321,6 +323,12 @@ public class WifiMetrics { LINK_PROBE_ELAPSED_TIME_MS_HISTOGRAM_BUCKETS); private final IntCounter mLinkProbeFailureReasonCounts = new IntCounter(); + /** + * Maps a String link probe experiment ID to the number of link probes that were sent for this + * experiment. + */ + private final ObjectCounter<String> mLinkProbeExperimentProbeCounts = new ObjectCounter<>(); + private final LinkedList<WifiUsabilityStatsEntry> mWifiUsabilityStatsEntriesList = new LinkedList<>(); private final LinkedList<WifiUsabilityStats> mWifiUsabilityStatsListBad = new LinkedList<>(); @@ -2749,6 +2757,7 @@ public class WifiMetrics { pw.println("mLinkProbeSuccessElapsedTimeMsHistogram:" + mLinkProbeSuccessElapsedTimeMsHistogram); pw.println("mLinkProbeFailureReasonCounts:" + mLinkProbeFailureReasonCounts); + pw.println("mLinkProbeExperimentProbeCounts:" + mLinkProbeExperimentProbeCounts); pw.println("mNetworkSelectionExperimentPairNumChoicesCounts:" + mNetworkSelectionExperimentPairNumChoicesCounts); @@ -2774,7 +2783,6 @@ public class WifiMetrics { + mWifiLogProto.numAddOrUpdateNetworkCalls); pw.println("mWifiLogProto.numEnableNetworkCalls=" + mWifiLogProto.numEnableNetworkCalls); - } } } @@ -3261,14 +3269,22 @@ public class WifiMetrics { mLinkProbeFailureSecondsSinceLastTxSuccessHistogram.toProto(); linkProbeStats.successElapsedTimeMsHistogram = mLinkProbeSuccessElapsedTimeMsHistogram.toProto(); - linkProbeStats.failureReasonCounts = - mLinkProbeFailureReasonCounts.toProto(LinkProbeFailureReasonCount.class, - (reason, count) -> { - LinkProbeFailureReasonCount c = new LinkProbeFailureReasonCount(); - c.failureReason = linkProbeFailureReasonToProto(reason); - c.count = count; - return c; - }); + linkProbeStats.failureReasonCounts = mLinkProbeFailureReasonCounts.toProto( + LinkProbeFailureReasonCount.class, + (reason, count) -> { + LinkProbeFailureReasonCount c = new LinkProbeFailureReasonCount(); + c.failureReason = linkProbeFailureReasonToProto(reason); + c.count = count; + return c; + }); + linkProbeStats.experimentProbeCounts = mLinkProbeExperimentProbeCounts.toProto( + ExperimentProbeCounts.class, + (experimentId, probeCount) -> { + ExperimentProbeCounts c = new ExperimentProbeCounts(); + c.experimentId = experimentId; + c.probeCount = probeCount; + return c; + }); mWifiLogProto.linkProbeStats = linkProbeStats; mWifiLogProto.networkSelectionExperimentDecisionsList = @@ -3483,6 +3499,7 @@ public class WifiMetrics { mLinkProbeFailureSecondsSinceLastTxSuccessHistogram.clear(); mLinkProbeSuccessElapsedTimeMsHistogram.clear(); mLinkProbeFailureReasonCounts.clear(); + mLinkProbeExperimentProbeCounts.clear(); mNetworkSelectionExperimentPairNumChoicesCounts.clear(); mWifiNetworkSuggestionApiLog.clear(); mWifiNetworkSuggestionApiLog.clear(); @@ -4734,6 +4751,15 @@ public class WifiMetrics { } /** + * Increments the number of probes triggered by the experiment `experimentId`. + */ + public void incrementLinkProbeExperimentProbeCount(String experimentId) { + synchronized (mLock) { + mLinkProbeExperimentProbeCounts.increment(experimentId); + } + } + + /** * Update wifi config store read duration. * * @param timeMs Time it took to complete the operation, in milliseconds diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java index 46fce6aa4..69dff1edd 100644 --- a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java @@ -3274,7 +3274,8 @@ public class ClientModeImplTest { connect(); // reset() should be called when RSSI polling is enabled and entering L2ConnectedState - verify(mLinkProbeManager).reset(); + verify(mLinkProbeManager).resetOnNewConnection(); // called first time here + verify(mLinkProbeManager, never()).resetOnScreenTurnedOn(); // not called verify(mLinkProbeManager).updateConnectionStats(any(), any()); mCmi.enableRssiPolling(false); @@ -3283,7 +3284,8 @@ public class ClientModeImplTest { // becomes enabled mCmi.enableRssiPolling(true); mLooper.dispatchAll(); - verify(mLinkProbeManager, times(2)).reset(); + verify(mLinkProbeManager, times(1)).resetOnNewConnection(); // verify not called again + verify(mLinkProbeManager).resetOnScreenTurnedOn(); // verify called here } /** diff --git a/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java b/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java index 614ec7e57..35f63149c 100644 --- a/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/LinkProbeManagerTest.java @@ -16,9 +16,12 @@ package com.android.server.wifi; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -40,6 +43,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashSet; + /** * Unit tests for LinkProbeManager */ @@ -102,7 +107,7 @@ public class LinkProbeManagerTest { */ @Test public void testLinkProbeTriggeredAndAcked() throws Exception { - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnNewConnection(); // initialize tx success counter mWifiInfo.txSuccess = 50; @@ -111,6 +116,7 @@ public class LinkProbeManagerTest { mLinkProbeManager.updateConnectionStats(mWifiInfo, TEST_IFACE_NAME); // should not probe yet verify(mWifiNative, never()).probeLink(any(), any(), any(), anyInt()); + verify(mWifiMetrics, never()).incrementLinkProbeExperimentProbeCount(any()); // tx success counter did not change since last update mWifiInfo.txSuccess = 50; @@ -130,6 +136,12 @@ public class LinkProbeManagerTest { ArgumentCaptor.forClass(WifiNative.SendMgmtFrameCallback.class); verify(mWifiNative).probeLink(eq(TEST_IFACE_NAME), any(), callbackCaptor.capture(), anyInt()); + ArgumentCaptor<String> experimentIdCaptor = ArgumentCaptor.forClass(String.class); + verify(mWifiMetrics, atLeastOnce()).incrementLinkProbeExperimentProbeCount( + experimentIdCaptor.capture()); + int len = LinkProbeManager.EXPERIMENT_DELAYS_MS.length; + int numExperimentIds = len * len * len; + assertEquals(numExperimentIds, new HashSet<>(experimentIdCaptor.getAllValues()).size()); callbackCaptor.getValue().onAck(TEST_ELAPSED_TIME_MS); verify(mWifiMetrics).logLinkProbeSuccess(TEST_TIMESTAMP_MS, timeDelta, rssi, linkSpeed, @@ -142,7 +154,7 @@ public class LinkProbeManagerTest { */ @Test public void testLinkProbeTriggeredAndFailed() throws Exception { - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnNewConnection(); // initialize tx success counter mWifiInfo.txSuccess = 50; @@ -203,7 +215,7 @@ public class LinkProbeManagerTest { */ @Test public void testLinkProbeNotTriggeredWhenTxSucceeded() throws Exception { - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnNewConnection(); // initialize tx success counter mWifiInfo.txSuccess = 50; @@ -243,7 +255,7 @@ public class LinkProbeManagerTest { eq(Settings.Global.WIFI_LINK_PROBING_ENABLED), anyInt())).thenReturn(0); mContentObserver.onChange(false); - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnNewConnection(); // initialize tx success counter mWifiInfo.txSuccess = 50; @@ -278,7 +290,7 @@ public class LinkProbeManagerTest { mResources.setBoolean(R.bool.config_wifi_link_probing_supported, false); initLinkProbeManager(); - mLinkProbeManager.reset(); + mLinkProbeManager.resetOnNewConnection(); // initialize tx success counter mWifiInfo.txSuccess = 50; diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java index 4fa4e48bb..88ff48838 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java @@ -21,11 +21,13 @@ import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_STATIONARY; import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN; import static com.android.server.wifi.WifiMetricsTestUtil.assertDeviceMobilityStatePnoScanStatsEqual; +import static com.android.server.wifi.WifiMetricsTestUtil.assertExperimentProbeCountsEqual; import static com.android.server.wifi.WifiMetricsTestUtil.assertHistogramBucketsEqual; import static com.android.server.wifi.WifiMetricsTestUtil.assertKeyCountsEqual; import static com.android.server.wifi.WifiMetricsTestUtil.assertLinkProbeFailureReasonCountsEqual; import static com.android.server.wifi.WifiMetricsTestUtil.assertLinkProbeStaEventsEqual; import static com.android.server.wifi.WifiMetricsTestUtil.buildDeviceMobilityStatePnoScanStats; +import static com.android.server.wifi.WifiMetricsTestUtil.buildExperimentProbeCounts; import static com.android.server.wifi.WifiMetricsTestUtil.buildHistogramBucketInt32; import static com.android.server.wifi.WifiMetricsTestUtil.buildInt32Count; import static com.android.server.wifi.WifiMetricsTestUtil.buildLinkProbeFailureReasonCount; @@ -85,6 +87,7 @@ import com.android.server.wifi.nano.WifiMetricsProto.DeviceMobilityStatePnoScanS import com.android.server.wifi.nano.WifiMetricsProto.HistogramBucketInt32; import com.android.server.wifi.nano.WifiMetricsProto.Int32Count; import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats; +import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats.ExperimentProbeCounts; import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats.LinkProbeFailureReasonCount; import com.android.server.wifi.nano.WifiMetricsProto.NetworkSelectionExperimentDecisions; import com.android.server.wifi.nano.WifiMetricsProto.PasspointProfileTypeCount; @@ -3457,6 +3460,32 @@ public class WifiMetricsTest { } /** + * Tests counting the number of link probes triggered per day for each experiment. + */ + @Test + public void testIncrementLinkProbeExperimentProbeCount() throws Exception { + String experimentId1 = "screenOnDelay=6000,noTxDelay=3000,delayBetweenProbes=9000," + + "rssiThreshold=-70,linkSpeedThreshold=15,"; + mWifiMetrics.incrementLinkProbeExperimentProbeCount(experimentId1); + + String experimentId2 = "screenOnDelay=9000,noTxDelay=12000,delayBetweenProbes=15000," + + "rssiThreshold=-72,linkSpeedThreshold=20,"; + mWifiMetrics.incrementLinkProbeExperimentProbeCount(experimentId2); + mWifiMetrics.incrementLinkProbeExperimentProbeCount(experimentId2); + + dumpProtoAndDeserialize(); + + ExperimentProbeCounts[] actual = mDecodedProto.linkProbeStats.experimentProbeCounts; + + ExperimentProbeCounts[] expected = { + buildExperimentProbeCounts(experimentId1, 1), + buildExperimentProbeCounts(experimentId2, 2) + }; + + assertExperimentProbeCountsEqual(expected, actual); + } + + /** * Tests logNetworkSelectionDecision() */ @Test diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java index 407d20932..0e9f661ca 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTestUtil.java @@ -23,6 +23,7 @@ import android.net.wifi.WifiManager; import com.android.server.wifi.nano.WifiMetricsProto.DeviceMobilityStatePnoScanStats; import com.android.server.wifi.nano.WifiMetricsProto.HistogramBucketInt32; import com.android.server.wifi.nano.WifiMetricsProto.Int32Count; +import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats.ExperimentProbeCounts; import com.android.server.wifi.nano.WifiMetricsProto.LinkProbeStats.LinkProbeFailureReasonCount; import com.android.server.wifi.nano.WifiMetricsProto.StaEvent; @@ -227,4 +228,41 @@ public class WifiMetricsTestUtil { } } } + + /** + * The constructor we wish ExperimentProbeCounts had. + */ + public static ExperimentProbeCounts buildExperimentProbeCounts( + String experimentId, int probeCount) { + ExperimentProbeCounts counts = new ExperimentProbeCounts(); + counts.experimentId = experimentId; + counts.probeCount = probeCount; + return counts; + } + + /** + * Asserts that the two arrays are equal (ignoring order), + * reporting any difference between them. + */ + public static void assertExperimentProbeCountsEqual( + ExperimentProbeCounts[] expected, ExperimentProbeCounts[] actual) { + + assertEquals("Number of ExperimentProbeCounts do not match!", + expected.length, actual.length); + + Arrays.sort(expected, Comparator.comparing(x -> x.experimentId)); + Arrays.sort(actual, Comparator.comparing(x -> x.experimentId)); + + for (int i = 0; i < expected.length; i++) { + ExperimentProbeCounts expectedCounts = expected[i]; + ExperimentProbeCounts actualCounts = actual[i]; + + assertEquals(String.format( + "ExperimentProbeCounts[%d].experimentId does not match!", i), + expectedCounts.experimentId, actualCounts.experimentId); + assertEquals(String.format( + "ExperimentProbeCounts[%d].probeCount does not match!", i), + expectedCounts.probeCount, actualCounts.probeCount); + } + } } |