| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.net; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.truth.Correspondence; |
| import com.google.common.truth.Correspondence.BinaryPredicate; |
| |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Stores Reporting API reports received by a test collector, providing helper methods for checking |
| * whether expected reports were actually received. |
| */ |
| class ReportingCollector { |
| private List<JSONObject> mReceivedReports = new ArrayList<JSONObject>(); |
| private Semaphore mReceivedReportsSemaphore = new Semaphore(0); |
| |
| /** |
| * Stores a batch of uploaded reports. |
| * @param payload the POST payload from the upload |
| * @return whether the payload was parsed successfully |
| */ |
| public boolean addReports(String payload) { |
| try { |
| JSONArray reports = new JSONArray(payload); |
| int elementCount = 0; |
| synchronized (mReceivedReports) { |
| for (int i = 0; i < reports.length(); i++) { |
| JSONObject element = reports.optJSONObject(i); |
| if (element != null) { |
| mReceivedReports.add(element); |
| elementCount++; |
| } |
| } |
| } |
| mReceivedReportsSemaphore.release(elementCount); |
| return true; |
| } catch (JSONException e) { |
| return false; |
| } |
| } |
| |
| public void assertContainsReport(String expectedString) { |
| JSONObject expectedJson; |
| |
| try { |
| expectedJson = new JSONObject(expectedString); |
| } catch (JSONException e) { |
| throw new IllegalArgumentException(e); |
| } |
| synchronized (mReceivedReports) { |
| assertThat(mReceivedReports) |
| .comparingElementsUsing( |
| Correspondence.from( |
| (BinaryPredicate<JSONObject, JSONObject>) |
| (actual, expected) -> |
| isJSONObjectSubset(expected, actual), |
| "is a subset of")) |
| .contains(expectedJson); |
| } |
| } |
| |
| /** Waits until the requested number of reports have been received, with a 5-second timeout. */ |
| public void waitForReports(int reportCount) { |
| final int timeoutSeconds = 5; |
| try { |
| mReceivedReportsSemaphore.tryAcquire(reportCount, timeoutSeconds, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| /** |
| * Checks whether one {@link JSONObject} is a subset of another. Any fields that appear in |
| * {@code lhs} must also appear in {@code rhs}, with the same value. There can be extra fields |
| * in {@code rhs}; if so, they are ignored. |
| */ |
| private boolean isJSONObjectSubset(JSONObject lhs, JSONObject rhs) { |
| Iterator<String> keys = lhs.keys(); |
| while (keys.hasNext()) { |
| String key = keys.next(); |
| Object lhsElement = lhs.opt(key); |
| Object rhsElement = rhs.opt(key); |
| |
| if (rhsElement == null) { |
| // lhs has an element that doesn't appear in rhs |
| return false; |
| } |
| |
| if (lhsElement instanceof JSONObject) { |
| if (!(rhsElement instanceof JSONObject)) { |
| return false; |
| } |
| return isJSONObjectSubset((JSONObject) lhsElement, (JSONObject) rhsElement); |
| } |
| |
| if (!lhsElement.equals(rhsElement)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| ; |