Author: pmouawad
Date: Tue Feb 26 19:50:43 2019
New Revision: 1854416
URL: http://svn.apache.org/viewvc?rev=1854416&view=rev
Log:
Improve a few unit tests and classes
This closes #451
Added:
jmeter/trunk/test/src/org/apache/jmeter/protocol/http/control/DNSCacheManagerSpec.groovy
Removed:
jmeter/trunk/test/src/org/apache/jmeter/protocol/http/control/TestDNSCacheManager.java
Modified:
jmeter/trunk/src/core/org/apache/jmeter/report/core/SampleComparator.java
jmeter/trunk/src/core/org/apache/jmeter/report/processor/FieldSampleComparator.java
jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java
jmeter/trunk/test/src/org/apache/jmeter/report/processor/FieldSampleComparatorSpec.groovy
jmeter/trunk/xdocs/changes.xml
Modified:
jmeter/trunk/src/core/org/apache/jmeter/report/core/SampleComparator.java
URL:
http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/report/core/SampleComparator.java?rev=1854416&r1=1854415&r2=1854416&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/report/core/SampleComparator.java
(original)
+++ jmeter/trunk/src/core/org/apache/jmeter/report/core/SampleComparator.java
Tue Feb 26 19:50:43 2019
@@ -19,7 +19,7 @@ package org.apache.jmeter.report.core;
/**
* Defines a comparator for {@link Sample} instances
- *
+ *
* @since 3.0
*/
public interface SampleComparator {
@@ -28,19 +28,17 @@ public interface SampleComparator {
* Compares to sample
* <p>
* Must return an long integer that define the relational order of the 2
- * compared samples :</p>
+ * compared samples:</p>
* <ul>
* <li>Negative long integer : s1 is lower than s2</li>
* <li>Zero long integer : s1 is strictly equal to s2</li>
* <li>Positive long integer : s1 is greater than s2</li>
* </ul>
- *
- * @param s1
- * The first sample to be compared
- * @param s2
- * The second sample to compared
+ *
+ * @param s1 The first sample to be compared
+ * @param s2 The second sample to compared
* @return A negative is <code>s1 < s2</code>, <code>0 if s1 =
s2</code>,
- * a positive integer if <code>s1 > s2</code>
+ * a positive integer if <code>s1 > s2</code>
*/
long compare(Sample s1, Sample s2);
@@ -55,10 +53,8 @@ public interface SampleComparator {
* Not that this function is the place to get sample column indexes for
* better performance
* </p>
- *
- * @param metadata
- * The sample metadata of the sample to be compared by this
- * instance
+ *
+ * @param metadata The metadata of the sample to be compared by this
instance
*/
void initialize(SampleMetadata metadata);
Modified:
jmeter/trunk/src/core/org/apache/jmeter/report/processor/FieldSampleComparator.java
URL:
http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/report/processor/FieldSampleComparator.java?rev=1854416&r1=1854415&r2=1854416&view=diff
==============================================================================
---
jmeter/trunk/src/core/org/apache/jmeter/report/processor/FieldSampleComparator.java
(original)
+++
jmeter/trunk/src/core/org/apache/jmeter/report/processor/FieldSampleComparator.java
Tue Feb 26 19:50:43 2019
@@ -25,7 +25,7 @@ import org.apache.jmeter.report.core.Sam
* @since 3.0
*/
public class FieldSampleComparator implements SampleComparator {
-
+
private int index;
private final String fieldName;
@@ -39,15 +39,9 @@ public class FieldSampleComparator imple
index = metadata.ensureIndexOf(fieldName);
}
- /*
- * (non-Javadoc)
- *
- * @see
- * org.apache.jmeter.report.csv.core.SampleComparator#compare(org.apache
- * .jmeter.report.csv.core.Sample,
org.apache.jmeter.report.csv.core.Sample)
- */
@Override
public long compare(Sample s1, Sample s2) {
- return s1.getData(long.class, index,
fieldName).compareTo(s2.getData(long.class, index, fieldName));
+ return s1.getData(long.class, index, fieldName)
+ .compareTo(s2.getData(long.class, index, fieldName));
}
}
Modified:
jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java
URL:
http://svn.apache.org/viewvc/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java?rev=1854416&r1=1854415&r2=1854416&view=diff
==============================================================================
---
jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java
(original)
+++
jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/control/DNSCacheManager.java
Tue Feb 26 19:50:43 2019
@@ -5,9 +5,9 @@
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -72,7 +72,7 @@ public class DNSCacheManager extends Con
private static final String SERVERS = "DNSCacheManager.servers"; //
$NON-NLS-1$
- private static final String HOSTS = "DNSCacheManager.hosts"; //
$NON-NLS-1$
+ private static final String HOSTS = "DNSCacheManager.hosts"; // $NON-NLS-1$
private static final String IS_CUSTOM_RESOLVER =
"DNSCacheManager.isCustomResolver"; // $NON-NLS-1$
//-- JMX tag values
@@ -85,7 +85,7 @@ public class DNSCacheManager extends Con
final Map<String, InetAddress[]> cache;
- transient Resolver resolver;
+ private transient Resolver resolver;
private transient int timeoutMs;
@@ -113,9 +113,6 @@ public class DNSCacheManager extends Con
return clone;
}
- /**
- * @return {@link Resolver}
- */
private Resolver createResolver() {
CollectionProperty dnsServers = getServers();
try {
@@ -140,7 +137,6 @@ public class DNSCacheManager extends Con
}
/**
- *
* Resolves address using system or custom DNS resolver
*/
@Override
@@ -154,29 +150,33 @@ public class DNSCacheManager extends Con
//
https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html
if (result != null || cache.containsKey(host)) {
if (log.isDebugEnabled()) {
- log.debug("Cache hit thr#{}: {} => {}",
JMeterContextService.getContext().getThreadNum(), host,
- Arrays.toString(result));
+ logCache("hit", host, result);
}
return result;
} else if (isStaticHost(host)) {
InetAddress[] staticAddresses = fromStaticHost(host);
if (log.isDebugEnabled()) {
- log.debug("Cache miss thr#{}: (static) {} => {}",
JMeterContextService.getContext().getThreadNum(), host,
- Arrays.toString(staticAddresses));
+ logCache("miss", host, staticAddresses);
}
cache.put(host, staticAddresses);
return staticAddresses;
} else {
InetAddress[] addresses = requestLookup(host);
if (log.isDebugEnabled()) {
- log.debug("Cache miss thr#{}: {} => {}",
JMeterContextService.getContext().getThreadNum(), host,
- Arrays.toString(addresses));
+ logCache("miss", host, addresses);
}
cache.put(host, addresses);
return addresses;
}
}
+ private void logCache(String hitOrMiss, String host, InetAddress[]
addresses) {
+ log.debug("Cache " + hitOrMiss + " thread#{}: {} => {}",
+ JMeterContextService.getContext().getThreadNum(),
+ host,
+ Arrays.toString(addresses));
+ }
+
private boolean isStaticHost(String host) {
JMeterProperty p = getProperty(HOSTS);
if (p instanceof NullProperty) {
@@ -209,32 +209,32 @@ public class DNSCacheManager extends Con
return new InetAddress[0];
}
CollectionProperty property = (CollectionProperty) p;
- PropertyIterator iterator = property.iterator();
- while (iterator.hasNext()) {
- StaticHost entry = (StaticHost)
((TestElementProperty)iterator.next()).getObjectValue();
- if (entry.getName().equals(host)) {
- List<InetAddress> addresses = new ArrayList<>();
- for (String address :
Arrays.asList(entry.getAddress().split("\\s*,\\s*"))) {
- try {
- final InetAddress[] requestLookup =
requestLookup(address);
- if (requestLookup == null) {
- addAsLiteralAddress(addresses, address);
- } else {
- addresses.addAll(Arrays.asList(requestLookup));
- }
- } catch (UnknownHostException e) {
+ for (JMeterProperty jMeterProperty : property) {
+ StaticHost entry = (StaticHost) ((TestElementProperty)
jMeterProperty).getObjectValue();
+ if (!entry.getName().equals(host)) {
+ continue; // try the next property
+ }
+
+ List<InetAddress> addresses = new ArrayList<>();
+ for (String address : entry.getAddress().split("\\s*,\\s*")) {
+ try {
+ final InetAddress[] requestLookup = requestLookup(address);
+ if (requestLookup == null) {
addAsLiteralAddress(addresses, address);
- log.warn("Couldn't resolve static address {} for host
{}", address, host, e);
+ } else {
+ addresses.addAll(Arrays.asList(requestLookup));
}
+ } catch (UnknownHostException e) {
+ addAsLiteralAddress(addresses, address);
+ log.warn("Couldn't resolve static address {} for host {}",
address, host, e);
}
- return addresses.toArray(new InetAddress[addresses.size()]);
}
+ return addresses.toArray(new InetAddress[addresses.size()]);
}
return new InetAddress[0];
}
- private void addAsLiteralAddress(List<InetAddress> addresses,
- String address) {
+ private void addAsLiteralAddress(List<InetAddress> addresses, String
address) {
try {
addresses.add(InetAddress.getByName(address));
} catch (UnknownHostException e) {
@@ -244,55 +244,61 @@ public class DNSCacheManager extends Con
/**
* Sends DNS request via system or custom DNS resolver
- * @param host Host
+ *
+ * @param host Host to lookup
* @return array of {@link InetAddress} or null if lookup did not return
result
*/
private InetAddress[] requestLookup(String host) throws
UnknownHostException {
InetAddress[] addresses = null;
+
if (isCustomResolver()) {
- ExtendedResolver extendedResolver = getResolver();
- if (extendedResolver != null) {
- if(extendedResolver.getResolvers().length > 0) {
- try {
- Lookup lookup = new Lookup(host, Type.A);
- lookup.setCache(lookupCache);
- if (timeoutMs > 0) {
- resolver.setTimeout(timeoutMs / 1000, timeoutMs %
1000);
- }
- lookup.setResolver(resolver);
- Record[] records = lookup.run();
- if (records == null || records.length == 0) {
- throw new UnknownHostException("Failed to resolve
host name: " + host);
- }
- addresses = new InetAddress[records.length];
- for (int i = 0; i < records.length; i++) {
- addresses[i] = ((ARecord) records[i]).getAddress();
- }
- } catch (TextParseException tpe) {
- log.debug("Failed to create Lookup object: {}",
tpe.toString());
- }
- return addresses;
- }
- } else {
- throw new UnknownHostException("Could not resolve host:"+host
- +", failed to initialize resolver"
- + " or no resolver found");
+ ExtendedResolver extendedResolver = getOrCreateResolver();
+ if (extendedResolver == null) {
+ throw new UnknownHostException("Could not resolve host:" + host
+ + ", failed to initialize resolver or no resolver
found");
+ } else if (extendedResolver.getResolvers().length > 0) {
+ addresses = customRequestLookup(host);
+ }
+ } else {
+ addresses = systemDefaultDnsResolver.resolve(host);
+ if (log.isDebugEnabled()) {
+ logCache("miss (resolved with system resolver)", host,
addresses);
}
}
- addresses = systemDefaultDnsResolver.resolve(host);
- if (log.isDebugEnabled()) {
- log.debug("Cache miss: {} Thread #{}, resolved with system
resolver into {}", host,
- JMeterContextService.getContext().getThreadNum(),
Arrays.toString(addresses));
+
+ return addresses;
+ }
+
+ private InetAddress[] customRequestLookup(String host) throws
UnknownHostException {
+ InetAddress[] addresses = null;
+ try {
+ Lookup lookup = new Lookup(host, Type.A);
+ lookup.setCache(lookupCache);
+ if (timeoutMs > 0) {
+ resolver.setTimeout(timeoutMs / 1000, timeoutMs % 1000);
+ }
+ lookup.setResolver(resolver);
+ Record[] records = lookup.run();
+ if (records == null || records.length == 0) {
+ throw new UnknownHostException("Failed to resolve host name: "
+ host);
+ }
+ addresses = new InetAddress[records.length];
+ for (int i = 0; i < records.length; i++) {
+ addresses[i] = ((ARecord) records[i]).getAddress();
+ }
+ } catch (TextParseException tpe) {
+ log.debug("Failed to create Lookup object: {}", tpe.toString());
}
return addresses;
}
/**
- * Tries to initialize resolver , otherwise sets initFailed to true
+ * Tries to initialize resolver, otherwise sets initFailed to true
+ *
* @return ExtendedResolver if init succeeded or null otherwise
*/
- private ExtendedResolver getResolver() {
- if(resolver == null && !initFailed) {
+ private ExtendedResolver getOrCreateResolver() {
+ if (resolver == null && !initFailed) {
resolver = createResolver();
}
return (ExtendedResolver) resolver;
@@ -331,6 +337,7 @@ public class DNSCacheManager extends Con
/**
* Add DNS Server
+ *
* @param dnsServer DNS Server
*/
public void addServer(String dnsServer) {
@@ -355,7 +362,8 @@ public class DNSCacheManager extends Con
/**
* Add static host
- * @param dnsHost DNS host
+ *
+ * @param dnsHost DNS host
* @param addresses Comma separated list of addresses
*/
public void addHost(String dnsHost, String addresses) {
@@ -372,7 +380,7 @@ public class DNSCacheManager extends Con
/**
* Clean DNS cache each iteration
- *
+ *
* @return boolean
*/
public boolean isClearEachIteration() {
@@ -382,8 +390,7 @@ public class DNSCacheManager extends Con
/**
* Clean DNS cache each iteration
*
- * @param clear
- * flag whether DNS cache should be cleared on each iteration
+ * @param clear flag whether DNS cache should be cleared on each iteration
*/
public void setClearEachIteration(boolean clear) {
setProperty(new BooleanProperty(CLEAR_CACHE_EACH_ITER, clear));
Added:
jmeter/trunk/test/src/org/apache/jmeter/protocol/http/control/DNSCacheManagerSpec.groovy
URL:
http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/protocol/http/control/DNSCacheManagerSpec.groovy?rev=1854416&view=auto
==============================================================================
---
jmeter/trunk/test/src/org/apache/jmeter/protocol/http/control/DNSCacheManagerSpec.groovy
(added)
+++
jmeter/trunk/test/src/org/apache/jmeter/protocol/http/control/DNSCacheManagerSpec.groovy
Tue Feb 26 19:50:43 2019
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License") you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.protocol.http.control
+
+import org.apache.jmeter.junit.spock.JMeterSpec
+import org.xbill.DNS.ExtendedResolver
+import spock.lang.IgnoreIf
+
+class DNSCacheManagerSpec extends JMeterSpec {
+
+ private static final String VALID_DNS_SERVER = "8.8.8.8"
+ private static final String INVALID_DNS_SERVER = "512.1.1.1"
+
+ static def localDNSResolverFailed() {
+ try {
+ new DNSCacheManager().resolve("apache.org")
+ return false
+ } catch (UnknownHostException uhe) {
+ return true
+ }
+ }
+
+ def sut = new DNSCacheManager()
+
+ def "A custom resolver with one host will resolve that host"() {
+ given:
+ sut.setCustomResolver(true)
+ sut.addHost("jmeter.example.org", "127.0.0.1")
+ expect:
+ sut.resolve("jmeter.example.org") ==
[InetAddress.getByName("127.0.0.1")].toArray()
+ }
+
+ def "A custom resolver with one host and an invalid DNS server will still
resolve that host"() {
+ given:
+ sut.setCustomResolver(true)
+ sut.addServer(INVALID_DNS_SERVER)
+ sut.addHost("localhost", "127.0.0.1")
+ expect:
+ sut.resolve("localhost") ==
[InetAddress.getByName("127.0.0.1")].toArray()
+ }
+
+ def "A custom resolver with one host with many addresses will resolve all
addresses for that host"() {
+ given:
+ sut.setCustomResolver(true)
+ sut.addHost("jmeter.example.org", "127.0.0.1, 1.2.3.4")
+ expect:
+ sut.resolve("jmeter.example.org") ==
+ [InetAddress.getByName("127.0.0.1"),
+ InetAddress.getByName("1.2.3.4")].toArray()
+ }
+
+ @IgnoreIf({ DNSCacheManagerSpec.localDNSResolverFailed() })
+ def "Clear removes custom resolver status and any added hosts"() {
+ given:
+ sut.setCustomResolver(true)
+ sut.addHost("apache.jmeter.org", "127.0.0.1")
+ sut.clear()
+ expect:
+ // uses real DNS server
+
sut.resolve("jmeter.apache.org").contains(InetAddress.getByName("jmeter.apache.org"))
+
!sut.resolve("jmeter.apache.org").contains(InetAddress.getByName("127.0.0.1"))
+ }
+
+ def "If using an invalid server resolve throws UnknownHostException"() {
+ given:
+ sut.addServer(INVALID_DNS_SERVER)
+ sut.setCustomResolver(true)
+ when:
+ sut.resolve("jmeter.apache.org")
+ then:
+ thrown(UnknownHostException)
+ sut.resolver == null
+ sut.initFailed
+ }
+
+ @IgnoreIf({
+
(Boolean.getBoolean("skip.test_TestDNSCacheManager.testWithCustomResolverAnd1Server")
+ || DNSCacheManagerSpec.localDNSResolverFailed())
+ })
+ def "Valid DNS resolves and caches with custom resolve true"() {
+ given:
+ sut.addServer(VALID_DNS_SERVER)
+ sut.setCustomResolver(true)
+ sut.setTimeoutMs(100)
+ when:
+ sut.resolve("jmeter.apache.org")
+ then:
+ sut.resolver != null
+ ((ExtendedResolver) sut.resolver).getResolvers().length == 1
+ sut.cache.size() == 1
+ }
+
+ def "Cache should be used where entries exist"() {
+ given:
+ sut.addServer(VALID_DNS_SERVER)
+ sut.setCustomResolver(true)
+ sut.setTimeoutMs(100)
+ when:
+ sut.cache.put("jmeter.apache.org", new InetAddress[0])
+ then:
+ sut.resolve("jmeter.apache.org") == new InetAddress[0]
+
+ when:
+ sut.cache.put("jmeter.apache.org", null)
+ then:
+ sut.resolve("jmeter.apache.org") == null
+ }
+
+ @IgnoreIf({ DNSCacheManagerSpec.localDNSResolverFailed() })
+ def "set custom resolver but without an address should use system
resolver"() {
+ given:
+ sut.setCustomResolver(true)
+ sut.setTimeoutMs(100)
+ when:
+ // This will use Default System DNS resolver
+ sut.resolve("jmeter.apache.org")
+ then:
+ sut.resolver != null
+ ((ExtendedResolver) sut.resolver).getResolvers().length == 0
+ }
+
+ def "Clones retain custom resolve and server info"() {
+ given:
+ sut.setCustomResolver(true)
+ sut.addServer(INVALID_DNS_SERVER)
+ DNSCacheManager clone = (DNSCacheManager) sut.clone()
+ clone.setTimeoutMs(100)
+ when:
+ clone.resolve("jmeter.apache.org")
+ then:
+ thrown(UnknownHostException)
+ clone.resolver == sut.resolver
+ }
+
+ @IgnoreIf({ DNSCacheManagerSpec.localDNSResolverFailed() })
+ def "Resolve Existing Host With System Default Dns Server"() {
+ given:
+ sut.setCustomResolver(false)
+ when:
+ InetAddress[] result = sut.resolve("www.example.org")
+ then:
+ sut.resolver == null
+ result != null
+ result.length > 0 // IPv4 and/or IPv6
+ }
+
+ def "Resolve Non-existing Host With System Default Dns Server"() {
+ given:
+ sut.setCustomResolver(false)
+ when:
+ sut.resolve("jmeterxxx.apache.org")
+ then:
+ thrown(UnknownHostException)
+ sut.resolver == null
+ }
+}
Modified:
jmeter/trunk/test/src/org/apache/jmeter/report/processor/FieldSampleComparatorSpec.groovy
URL:
http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/report/processor/FieldSampleComparatorSpec.groovy?rev=1854416&r1=1854415&r2=1854416&view=diff
==============================================================================
---
jmeter/trunk/test/src/org/apache/jmeter/report/processor/FieldSampleComparatorSpec.groovy
(original)
+++
jmeter/trunk/test/src/org/apache/jmeter/report/processor/FieldSampleComparatorSpec.groovy
Tue Feb 26 19:50:43 2019
@@ -23,18 +23,41 @@ import spock.lang.Specification
class FieldSampleComparatorSpec extends Specification {
- def sampleMetadata = new SampleMetadata(',' as char, "test")
- def comparator = new FieldSampleComparator("test")
+ static char separator = ',' as char
+ def multiColSampleMeta = new SampleMetadata(separator, "col1", "col2")
+
def testCompare() {
given:
- def s1 = new Sample(0, sampleMetadata, "1")
- def s2 = new Sample(1, sampleMetadata, "2")
- comparator.initialize(sampleMetadata)
+ def sampleMetadata = new SampleMetadata(separator, "col1")
+ def firstRow = new Sample(0, sampleMetadata, "1")
+ def secondRow = new Sample(1, sampleMetadata, "2")
+ def sut = new FieldSampleComparator("col1")
+ sut.initialize(sampleMetadata)
+ expect:
+ sut.compare(firstRow, secondRow) < 0
+ sut.compare(secondRow, firstRow) > 0
+ sut.compare(firstRow, firstRow) == 0
+ sut.compare(secondRow, secondRow) == 0
+ }
+
+ def "initialize ensures correct column is compared"() {
+ given:
+ def sut = new FieldSampleComparator("col2")
+ def firstRow = new Sample(0, multiColSampleMeta, "1", "3")
+ def secondRow = new Sample(1, multiColSampleMeta, "2", "3")
+ sut.initialize(multiColSampleMeta)
expect:
- comparator.compare(s1, s2) < 0
- comparator.compare(s2, s1) > 0
- comparator.compare(s1, s1) == 0
- comparator.compare(s2, s2) == 0
+ sut.compare(firstRow, secondRow) == 0
}
+
+ def "Incorrectly uses first column if initialize isn't called"() {
+ given:
+ def sut = new FieldSampleComparator("col2")
+ def firstRow = new Sample(0, multiColSampleMeta, "1", "3")
+ def secondRow = new Sample(1, multiColSampleMeta, "2", "3")
+ expect:
+ sut.compare(firstRow, secondRow) != 0
+ }
+
}
Modified: jmeter/trunk/xdocs/changes.xml
URL:
http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1854416&r1=1854415&r2=1854416&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Tue Feb 26 19:50:43 2019
@@ -117,6 +117,7 @@ Summary
<li><bug>63203</bug>Unit Tests : Replace use of <code>@Deprecated</code>
by <code>@VisibleForTesting</code> for methods/constructors/classes made public
for Unit Testing only</li>
<li><pr>449</pr>Refactor and Test
ResponseTimePercentilesOverTimeGraphConsumer. Contributed by Graham Russell
(graham at ham1.co.uk)</li>
<li><pr>450</pr>Abstract graph consumer improvements. Contributed by
Graham Russell (graham at ham1.co.uk)</li>
+ <li><pr>451</pr>Improve a few unit tests and classes. Contributed by
Graham Russell (graham at ham1.co.uk)</li>
</ul>
<!-- =================== Bug fixes =================== -->