1 // Copyright 2018 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net; 6 7 import static com.google.common.truth.Truth.assertThat; 8 9 import com.google.common.truth.Correspondence; 10 import com.google.common.truth.Correspondence.BinaryPredicate; 11 12 import org.json.JSONArray; 13 import org.json.JSONException; 14 import org.json.JSONObject; 15 16 import java.util.ArrayList; 17 import java.util.Iterator; 18 import java.util.List; 19 import java.util.concurrent.Semaphore; 20 import java.util.concurrent.TimeUnit; 21 22 /** 23 * Stores Reporting API reports received by a test collector, providing helper methods for checking 24 * whether expected reports were actually received. 25 */ 26 class ReportingCollector { 27 private List<JSONObject> mReceivedReports = new ArrayList<JSONObject>(); 28 private Semaphore mReceivedReportsSemaphore = new Semaphore(0); 29 30 /** 31 * Stores a batch of uploaded reports. 32 * @param payload the POST payload from the upload 33 * @return whether the payload was parsed successfully 34 */ addReports(String payload)35 public boolean addReports(String payload) { 36 try { 37 JSONArray reports = new JSONArray(payload); 38 int elementCount = 0; 39 synchronized (mReceivedReports) { 40 for (int i = 0; i < reports.length(); i++) { 41 JSONObject element = reports.optJSONObject(i); 42 if (element != null) { 43 mReceivedReports.add(element); 44 elementCount++; 45 } 46 } 47 } 48 mReceivedReportsSemaphore.release(elementCount); 49 return true; 50 } catch (JSONException e) { 51 return false; 52 } 53 } 54 assertContainsReport(String expectedString)55 public void assertContainsReport(String expectedString) { 56 JSONObject expectedJson; 57 58 try { 59 expectedJson = new JSONObject(expectedString); 60 } catch (JSONException e) { 61 throw new IllegalArgumentException(e); 62 } 63 synchronized (mReceivedReports) { 64 assertThat(mReceivedReports) 65 .comparingElementsUsing( 66 Correspondence.from( 67 (BinaryPredicate<JSONObject, JSONObject>) 68 (actual, expected) -> 69 isJSONObjectSubset(expected, actual), 70 "is a subset of")) 71 .contains(expectedJson); 72 } 73 } 74 75 /** Waits until the requested number of reports have been received, with a 5-second timeout. */ waitForReports(int reportCount)76 public void waitForReports(int reportCount) { 77 final int timeoutSeconds = 5; 78 try { 79 mReceivedReportsSemaphore.tryAcquire(reportCount, timeoutSeconds, TimeUnit.SECONDS); 80 } catch (InterruptedException e) { 81 } 82 } 83 84 /** 85 * Checks whether one {@link JSONObject} is a subset of another. Any fields that appear in 86 * {@code lhs} must also appear in {@code rhs}, with the same value. There can be extra fields 87 * in {@code rhs}; if so, they are ignored. 88 */ isJSONObjectSubset(JSONObject lhs, JSONObject rhs)89 private boolean isJSONObjectSubset(JSONObject lhs, JSONObject rhs) { 90 Iterator<String> keys = lhs.keys(); 91 while (keys.hasNext()) { 92 String key = keys.next(); 93 Object lhsElement = lhs.opt(key); 94 Object rhsElement = rhs.opt(key); 95 96 if (rhsElement == null) { 97 // lhs has an element that doesn't appear in rhs 98 return false; 99 } 100 101 if (lhsElement instanceof JSONObject) { 102 if (!(rhsElement instanceof JSONObject)) { 103 return false; 104 } 105 return isJSONObjectSubset((JSONObject) lhsElement, (JSONObject) rhsElement); 106 } 107 108 if (!lhsElement.equals(rhsElement)) { 109 return false; 110 } 111 } 112 return true; 113 } 114 } 115 ; 116